aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'overlord')
-rw-r--r--overlord/__init__.py1
-rw-r--r--overlord/api.py452
-rw-r--r--overlord/cli.py312
-rw-r--r--overlord/config.py441
-rw-r--r--overlord/constants.py45
-rw-r--r--overlord/db.py353
-rw-r--r--overlord/dbbase.py276
-rw-r--r--overlord/debug.py515
-rw-r--r--overlord/makeconf.py269
-rw-r--r--overlord/overlays/__init__.py1
-rw-r--r--overlord/overlays/bzr.py70
-rw-r--r--overlord/overlays/cvs.py105
-rw-r--r--overlord/overlays/darcs.py69
-rw-r--r--overlord/overlays/g_common.py75
-rw-r--r--overlord/overlays/git.py79
-rw-r--r--overlord/overlays/mercurial.py69
-rw-r--r--overlord/overlays/overlay.py419
-rw-r--r--overlord/overlays/rsync.py75
-rw-r--r--overlord/overlays/source.py161
-rw-r--r--overlord/overlays/svn.py86
-rw-r--r--overlord/overlays/tar.py214
-rw-r--r--overlord/tests/dtest.py90
-rw-r--r--overlord/tests/external.py136
-rw-r--r--overlord/tests/pylintrc19
-rw-r--r--overlord/tests/testfiles/global-overlays.xml30
-rw-r--r--overlord/tests/testfiles/layman-test.tar.bz2bin0 -> 845 bytes
-rw-r--r--overlord/tests/testfiles/make.conf345
-rw-r--r--overlord/tests/testfiles/overlays_bug_184449.xml19
-rw-r--r--overlord/tests/testfiles/overlays_bug_286290.xml13
-rw-r--r--overlord/tests/testfiles/subpath-1.xml19
-rw-r--r--overlord/tests/testfiles/subpath-2.xml22
-rw-r--r--overlord/utils.py186
-rw-r--r--overlord/version.py26
33 files changed, 4992 insertions, 0 deletions
diff --git a/overlord/__init__.py b/overlord/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/overlord/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/overlord/api.py b/overlord/api.py
new file mode 100644
index 0000000..936af9b
--- /dev/null
+++ b/overlord/api.py
@@ -0,0 +1,452 @@
+#!python
+# -*- coding: utf-8 -*-
+
+"""
+ Overlord - A UTILITY TO SELECT AND UPDATE GENTOO OVERLAYS
+ Distributed under the terms of the GNU General Public License v2
+
+ Copyright:
+ (c) 2010 - 2011 Brian Dolbec
+ Distributed under the terms of the GNU General Public License v2
+
+ Author(s):
+ Brian Dolbec <dol-sen@sourceforge.net>
+
+"""
+
+from sys import stderr
+import os
+
+from overlord.config import BareConfig
+#from overlord.action import Sync
+
+from overlord.dbbase import UnknownOverlayException
+from overlord.db import DB, RemoteDB
+#from overlord.utils import path, delete_empty_directory
+from overlord.debug import OUT
+
+# give them some values for now, these are from the packagekit backend
+# TODO establish some proper errors for the api.
+ERROR_REPO_NOT_FOUND = -1
+ERROR_INTERNAL_ERROR = -2
+UNKNOWN_REPO_ID = "Repo ID '%s' " + \
+ "is not listed in the current available overlays list"
+
+# In order to redirect output you need to get a Message class instance with the
+# stderr, stdout, stddebug directed to where you want.
+# eg: output = Message('overlord', err=mystderr, dbg=mydebug, out=myoutput)
+# there are many more options available, refer to debug.py Message class
+
+
+class OverlordAPI(object):
+ """High level class to hold and run a overlord instance for use by
+ API consumer apps, guis, etc.
+ """
+
+ def __init__(self, config=None, report_errors=False, output=None):
+ """
+ @param configfile: optional config file to use instead of the default.
+ can be a BareConfig or ArgsParser config class.
+ default is BareConfig(output=output)
+ @param report_errors: optional bool to silence some error reporting to stdout
+ default is False
+ @param output: optional Message class instance created with your settings.
+ default is Message(module='overlord') other params are defaults.
+ """
+
+ self.output = output if output else OUT
+
+ self.config = config if config else BareConfig(output=output)
+
+ self.report_errors = report_errors
+
+ # get installed and available dbs
+ self._installed_db = None
+ self._installed_ids = None
+ self._available_db = None
+ self._available_ids = None
+ self._error_messages = []
+ self.sync_results = []
+
+
+ def is_repo(self, ovl):
+ """validates that the ovl given is a known repo id
+
+ @param ovl: repo id
+ @type ovl: str
+ @rtype boolean
+ """
+ return ovl in self.get_available()
+
+
+ def is_installed(self, ovl):
+ """checks that ovl is a known installed repo id
+
+ @param ovl: repo id
+ @type ovl: str
+ @rtype boolean
+ """
+ return ovl in self.get_installed()
+
+
+ @staticmethod
+ def _check_repo_type( repos, caller):
+ """internal function that validates the repos parameter,
+ converting a string to a list[string] if it is not already a list.
+ produces and error message if it is any other type
+ returns repos as list always"""
+ if isinstance(repos, basestring):
+ repos = [repos]
+ # else assume it is an iterable, if not it will error
+ return repos
+
+
+ def delete_repos(self, repos):
+ """delete the selected repo from the system
+
+ @type repos: list of strings or string
+ @param repos: ['repo-id1', ...] or 'repo-id'
+ @param output: method to handle output if desired
+ @rtype dict
+ """
+ repos = self._check_repo_type(repos, "delete_repo")
+ results = []
+ for ovl in repos:
+ if not self.is_installed(ovl):
+ results.append(True)
+ break
+ if not self.is_repo(ovl):
+ self._error(1, UNKNOWN_REPO_ID %ovl)
+ results.append(False)
+ break
+ try:
+ self._get_installed_db().delete(self._get_installed_db().select(ovl))
+ results.append(True)
+ except Exception, e:
+ self._error(ERROR_INTERNAL_ERROR,
+ "Failed to disable repository '"+ovl+"':\n"+str(e))
+ results.append(False)
+ self.get_installed(reload=True)
+ if False in results:
+ return False
+ return True
+
+
+ def add_repos(self, repos):
+ """installs the seleted repo id
+
+ @type repos: list of strings or string
+ @param repos: ['repo-id', ...] or 'repo-id'
+ @param output: method to handle output if desired
+ @rtype dict
+ """
+ repos = self._check_repo_type(repos, "add_repo")
+ results = []
+ for ovl in repos:
+ if self.is_installed(ovl):
+ results.append(True)
+ break
+ if not self.is_repo(ovl):
+ self._error(1, UNKNOWN_REPO_ID %ovl)
+ results.append(False)
+ break
+ try:
+ self._get_installed_db().add(self._get_remote_db().select(ovl), quiet=True)
+ results.append(True)
+ except Exception, e:
+ self._error(ERROR_INTERNAL_ERROR,
+ "Failed to enable repository '"+ovl+"' : "+str(e))
+ results.append(False)
+ self.get_installed(reload=True)
+ if False in results:
+ return False
+ return True
+
+
+ def get_all_info(self, repos, local=False):
+ """retrieves the recorded information about the repo(s)
+ specified by repo-id
+
+ @type repos: list of strings or string
+ @param repos: ['repo-id1', ...] or 'repo-id'
+ @rtype list of tuples [(str, bool, bool),...]
+ @return: dictionary of dictionaries
+ {'ovl1':
+ {'name': str,
+ 'owner_name': str,
+ 'owner_email': str,
+ ' homepage': str,
+ 'description': str,
+ 'src_uris': list of str ['uri1',...]
+ 'src_type': str,
+ 'priority': int,
+ 'quality': str
+ 'status':,
+ 'official': bool,
+ 'supported': bool,
+ },
+ 'ovl2': {...}
+ }
+ """
+
+ repos = self._check_repo_type(repos, "get_info")
+ result = {}
+
+ if local:
+ db = self._get_installed_db()
+ else:
+ db = self._get_remote_db()
+
+ for ovl in repos:
+ if not self.is_repo(ovl):
+ self._error(1, UNKNOWN_REPO_ID %ovl)
+ result[ovl] = ('', False, False)
+ try:
+ overlay = db.select(ovl)
+ except UnknownOverlayException, error:
+ self._error(2, "Error: %s" %str(error))
+ result[ovl] = ('', False, False)
+ else:
+ result[ovl] = {
+ 'name': overlay.name,
+ 'owner_name': overlay.owner_name,
+ 'owner_email': overlay.owner_email,
+ 'homepage': overlay.homepage,
+ 'description': overlay.description,
+ #'src_uris': [e.src for e in overlay.sources],
+ 'src_uris': overlay.source_uris(),
+ 'src_types': overlay.source_types(),
+ #'src_types': [e.type for e in overlay.sources],
+ 'priority': overlay.priority,
+ 'quality': overlay.quality,
+ 'status': overlay.status,
+ 'official': overlay.is_official(),
+ 'supported': overlay.is_supported(),
+ }
+
+ return result
+
+
+ def get_info_str(self, repos, local=True, verbose=False, width=0):
+ """retrieves the string representation of the recorded information
+ about the repo(s) specified by ovl
+
+ @type repos: list of strings or string
+ @param repos: ['repo-id1', ...] or 'repo-id'
+ @rtype list of tuples [(str, bool, bool),...]
+ @return: dictionary {'repo-id': (info string, official, supported)}
+ """
+ repos = self._check_repo_type(repos, "get_info")
+ result = {}
+
+ if local:
+ db = self._get_installed_db()
+ else:
+ db = self._get_remote_db()
+
+ for ovl in repos:
+ if not self.is_repo(ovl):
+ self._error(1, UNKNOWN_REPO_ID % ovl)
+ result[ovl] = ('', False, False)
+ try:
+ overlay = db.select(ovl)
+ #print "overlay = ", ovl
+ #print overlay
+ except UnknownOverlayException, error:
+ #print "ERRORS", str(error)
+ self._error(2, "Error: %s" %str(error))
+ result[ovl] = ('', False, False)
+ else:
+ # Is the overlay supported?
+ if verbose:
+ info = overlay.__str__()
+ else:
+ info = overlay.short_list(width)
+ official = overlay.is_official()
+ supported = overlay.is_supported()
+ result[ovl] = (info, official, supported)
+
+ return result
+
+ def get_info_list(self, local=True, verbose=False, width=0):
+ """retrieves the string representation of the recorded information
+ about the repo(s) specified by ovl
+
+ @param local: bool (defaults to True)
+ @param verbose: bool(defaults to False)
+ @param width: int (defaults to 0)
+ @rtype list of tuples [(str, bool, bool),...]
+ @return: list [(info string, official, supported),...]
+ """
+
+ if local:
+ return self._get_installed_db().list(verbose=verbose, width=width)
+ else:
+ return self._get_remote_db().list(verbose=verbose, width=width)
+
+
+ def sync(self, repos, output_results=True):
+ """syncs the specified repo(s) specified by repos
+
+ @type repos: list of strings or string
+ @param repos: ['repo-id1', ...] or 'repo-id'
+ @rtype bool or {'repo-id': bool,...}
+ """
+ fatals = []
+ warnings = []
+ success = []
+ repos = self._check_repo_type(repos, "sync")
+ db = self._get_installed_db()
+
+ for ovl in repos:
+ try:
+ odb = db.select(ovl)
+ except UnknownOverlayException, error:
+ self._error(1,"Sync(), failed to select %s overlay. Original error was: %s" %(ovl, str(error)))
+ continue
+
+ try:
+ ordb = self._get_remote_db().select(ovl)
+ except UnknownOverlayException:
+ message = 'Overlay "%s" could not be found in the remote lists.\n' \
+ 'Please check if it has been renamed and re-add if necessary.' % ovl
+ warnings.append((ovl, message))
+ else:
+ current_src = odb.sources[0].src
+ available_srcs = set(e.src for e in ordb.sources)
+ if ordb and odb and not current_src in available_srcs:
+ if len(available_srcs) == 1:
+ plural = ''
+ candidates = ' %s' % tuple(available_srcs)[0]
+ else:
+ plural = 's'
+ candidates = '\n'.join((' %d. %s' % (ovl + 1, v)) for ovl, v in enumerate(available_srcs))
+
+ warnings.append((ovl,
+ 'The source of the overlay "%(repo_name)s" seems to have changed.\n'
+ 'You currently sync from\n'
+ '\n'
+ ' %(current_src)s\n'
+ '\n'
+ 'while the remote lists report\n'
+ '\n'
+ '%(candidates)s\n'
+ '\n'
+ 'as correct location%(plural)s.\n'
+ 'Please consider removing and re-adding the overlay.' , {
+ 'repo_name':ovl,
+ 'current_src':current_src,
+ 'candidates':candidates,
+ 'plural':plural,
+ }))
+
+ try:
+ db.sync(ovl, self.config['quiet'])
+ success.append((ovl,'Successfully synchronized overlay "' + ovl + '".'))
+ except Exception, error:
+ fatals.append((ovl,
+ 'Failed to sync overlay "' + ovl + '".\nError was: '
+ + str(error)))
+
+ if output_results:
+ if success:
+ self.output.info('\nSucceeded:\n------\n', 3)
+ for ovl, result in success:
+ self.output.info(result, 3)
+
+ if warnings:
+ self.output.warn('\nWarnings:\n------\n', 2)
+ for ovl, result in warnings:
+ self.output.warn(result + '\n', 2)
+
+ if fatals:
+ self.output.error('\nErrors:\n------\n')
+ for ovl, result in fatals:
+ self.output.error(result + '\n')
+ return False
+
+ self.sync_results = (success, warnings, fatals)
+
+ return True
+
+
+ def fetch_remote_list(self):
+ """Fetches the latest remote overlay list"""
+ try:
+ self._get_remote_db().cache()
+ except Exception, error:
+ self._error(-1,'Failed to fetch overlay list!\n Original Error was: '
+ + str(error))
+ return False
+ self.get_available(reload=True)
+ return True
+
+
+ def get_available(self, reload=False):
+ """returns the list of available overlays"""
+ if self._available_ids is None or reload:
+ self._available_ids = self._get_remote_db(reload).list_ids()
+ return self._available_ids[:] or ['None']
+
+
+ def get_installed(self, reload=False):
+ """returns the list of installed overlays"""
+ if self._installed_ids is None or reload:
+ self._installed_ids = self._get_installed_db(reload).list_ids()
+ return self._installed_ids[:]
+
+
+ def _get_installed_db(self, reload=False):
+ """returns the list of installed overlays"""
+ if not self._installed_db or reload:
+ self._installed_db = DB(self.config)
+ return self._installed_db
+
+
+ def _get_remote_db(self, reload=False):
+ """returns the list of installed overlays"""
+ if self._available_db is None or reload:
+ self._available_db = RemoteDB(self.config)
+ return self._available_db
+
+
+ def reload(self):
+ """reloads the installed and remote db's to the data on disk"""
+ result = self.get_available(reload=True)
+ result = self.get_installed(reload=True)
+
+
+ def _error(self, num, message):
+ """outputs the error to the pre-determined output
+ defaults to stderr. This method may be removed, is here for now
+ due to code taken from the packagekit backend.
+ """
+ msg = "Error: %d," % num, message
+ self._error_messages.append(msg)
+ if self.report_errors:
+ print >>stderr, msg
+
+
+ def get_errors(self):
+ """returns any warning or fatal messages that occurred during
+ an operation and resets it back to None
+
+ @rtype: list
+ @return: list of error strings
+ """
+ if self._error_messages:
+ messages = self._error_messages[:]
+ self._error_messages = []
+ return messages
+
+
+def create_fd():
+ """creates file descriptor pairs an opens them ready for
+ use in place of stdin, stdout, stderr.
+ """
+ fd_r, fd_w = os.pipe()
+ write = os.fdopen(fd_w, 'w')
+ rread = os.fdopen(fd_r, 'r')
+ return (read, write, fd_r, fd_w)
+
+
diff --git a/overlord/cli.py b/overlord/cli.py
new file mode 100644
index 0000000..501dd65
--- /dev/null
+++ b/overlord/cli.py
@@ -0,0 +1,312 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord ACTIONS
+#################################################################################
+# File: cli.py
+#
+# Handles overlord actions via the command line interface.
+#
+# Copyright:
+# (c) 2010 - 2011
+# Gunnar Wrobel
+# Brian Dolbec
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Brian Dolbec <brian.dolbec@gmail.com
+#
+''' Provides the command line actions that can be performed by overlord.'''
+
+__version__ = "$Id: cli.py 2011-01-15 23:52 PST Brian Dolbec$"
+
+
+import os, sys
+
+from overlord.api import OverlordAPI
+from overlord.utils import (decode_selection, encoder, get_encoding,
+ pad, terminal_width)
+from overlord.constants import NOT_OFFICIAL_MSG, NOT_SUPPORTED_MSG
+
+
+
+class ListPrinter(object):
+ def __init__(self, config):
+ self.config = config
+ self.output = self.config['output']
+ if not self.config['width']:
+ self.width = terminal_width()
+ else:
+ self.width = self.config['width']
+ self.srclen = self.width - 43
+ self._encoding_ = get_encoding(self.output)
+ if config['verbose']:
+ self.my_lister = self.short_list # self.long_list
+ else:
+ self.my_lister = self.short_list
+
+ def print_shortdict(self, info, complain):
+ #print "ListPrinter.print_shortdict()",info, "\n\n"
+ overlays = sorted(info)
+ #print "ids =======>", overlays, "\n"
+ for ovl in overlays:
+ overlay = info[ovl]
+ #print "overlay =", overlay
+ summary, supported, official = overlay
+ self.print_overlay(summary, supported, official, complain)
+
+ def print_shortlist(self, info, complain):
+ #print "ListPrinter.print_shortlist()",info
+ for summary, supported, official in info:
+ self.print_overlay(summary, supported, official, complain)
+
+
+ def print_fulldict(self, info, complain):
+ ids = sorted(info)
+ #print "ids =======>", ids, "\n"
+ for ovl in ids:
+ overlay = info[ovl]
+ #print overlay
+ self.print_overlay(self.my_lister(overlay),
+ overlay['supported'],
+ overlay['official'],
+ complain)
+
+
+ def print_overlay(self, summary, supported, official, complain):
+ # Is the overlay supported?
+ if supported:
+ # Is this an official overlay?
+ if official:
+ self.output.info(summary, 1)
+ # Unofficial overlays will only be listed if we are not
+ # checking or listing verbose
+ elif complain:
+ # Give a reason why this is marked yellow if it is a verbose
+ # listing
+ if self.config['verbose']:
+ self.output.warn(NOT_OFFICIAL_MSG, 1)
+ self.output.warn(summary, 1)
+ # Unsupported overlays will only be listed if we are not checking
+ # or listing verbose
+ elif complain:
+ # Give a reason why this is marked red if it is a verbose
+ # listing
+ if self.config['verbose']:
+ self.output.error(NOT_SUPPORTED_MSG)
+ self.output.error(summary)
+
+
+ def short_list(self, overlay):
+ '''
+ >>> print short_list(overlay)
+ wrobel [Subversion] (https://o.g.o/svn/dev/wrobel )
+ '''
+ name = pad(overlay['name'], 25)
+
+ if len(set(e for e in overlay['src_types'])) == 1:
+ _type = overlay['src_types'][0]
+ else:
+ _type = '%s/..' % overlay['src_type'][0]
+ mtype = ' [' + pad(_type, 10) + ']'
+
+ source = ', '.join(overlay['src_uris'])
+
+ if len(source) > self.srclen:
+ source = source.replace("overlays.gentoo.org", "o.g.o")
+ source = ' (' + pad(source, self.srclen) + ')'
+
+ return encoder(name + mtype + source, self._encoding_)
+
+
+class Main(object):
+ '''Performs the actions the user selected.
+ '''
+
+ def __init__(self, config):
+ self.config = config
+ #print "config.keys()", config.keys()
+ self.output = config['output']
+ self.api = OverlordAPI(config,
+ report_errors=True,
+ output=config.output)
+ # Given in order of precedence
+ self.actions = [('fetch', 'Fetch'),
+ ('add', 'Add'),
+ ('sync', 'Sync'),
+ ('info', 'Info'),
+ ('sync_all', 'Sync'),
+ ('delete', 'Delete'),
+ ('list', 'ListRemote'),
+ ('list_local', 'ListLocal'),]
+
+ def __call__(self):
+ # Make fetching the overlay list a default action
+ if not 'nofetch' in self.config.keys():
+ # Actions that implicitely call the fetch operation before
+ fetch_actions = ['sync', 'sync_all', 'list']
+ for i in fetch_actions:
+ if i in self.config.keys():
+ # Implicitely call fetch, break loop
+ self.Fetch()
+ break
+
+ result = 0
+
+ # Set the umask
+ umask = self.config['umask']
+ try:
+ new_umask = int(umask, 8)
+ old_umask = os.umask(new_umask)
+ except Exception, error:
+ self.output.die('Failed setting to umask "' + umask +
+ '"!\nError was: ' + str(error))
+
+ for action in self.actions:
+
+ self.output.debug('Checking for action', 7)
+
+ if action[0] in self.config.keys():
+ try:
+ result += getattr(self, action[1])()
+ except Exception, error:
+ self.output.error(self.api.get_errors())
+ result = -1 # So it cannot remain 0, i.e. success
+ break
+
+ # Reset umask
+ os.umask(old_umask)
+
+ if not result:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+
+ def Fetch(self):
+ ''' Fetches the overlay listing.
+ '''
+ self.output.info("Fetching remote list,...", 2)
+ result = self.api.fetch_remote_list()
+ if result:
+ self.output.info('Ok', 2)
+ else:
+ errors = self.api.get_errors()
+ self.output.warn('Download failed.\nError was: '
+ + str('\n'.join(errors)), 2)
+ return result
+
+
+ def Add(self):
+ ''' Adds the selected overlays.
+ '''
+ selection = decode_selection(self.config['add'])
+ if 'ALL' in selection:
+ selection = self.api.get_available()
+ self.output.debug('Adding selected overlays', 6)
+ result = self.api.add_repos(selection)
+ if result:
+ self.output.info('Successfully added overlay(s) '+\
+ ', '.join(selection) +'.', 2)
+ else:
+ errors = self.api.get_errors()
+ self.output.warn('Failed to add overlay(s).\nError was: '
+ + str('\n'.join(errors)), 2)
+ return result
+
+
+
+ def Sync(self):
+ ''' Syncs the selected overlays.
+ '''
+ # Note api.sync() defaults to printing results
+ selection = decode_selection(self.config['sync'])
+ if self.config['sync_all'] or 'ALL' in selection:
+ selection = self.api.get_installed()
+ self.output.debug('Updating selected overlays', 6)
+ return self.api.sync(selection)
+
+
+ def Delete(self):
+ ''' Deletes the selected overlays.
+ '''
+ selection = decode_selection(self.config['delete'])
+ if 'ALL' in selection:
+ selection = self.api.get_installed()
+ self.output.debug('Deleting selected overlays', 6)
+ result = self.api.delete_repos(selection)
+ if result:
+ self.output.info('Successfully deleted overlay(s) ' +\
+ ', '.join(selection) + '.', 2)
+ else:
+ errors = self.api.get_errors()
+ self.output.warn('Failed to delete overlay(s).\nError was: '
+ + str('\n'.join(errors)), 2)
+ return result
+
+
+ def Info(self):
+ ''' Print information about the specified overlays.
+ '''
+ selection = decode_selection(self.config['info'])
+ if 'ALL' in selection:
+ selection = self.api.get_available()
+
+ list_printer = ListPrinter(self.config)
+ _complain = self.config['nocheck'] or self.config['verbose']
+
+ info = self.api.get_info_str(selection, local=False,
+ verbose=True, width=list_printer.width)
+ #print "info =", info
+ list_printer.print_shortdict(info, complain=_complain)
+
+ return info != {}
+
+
+ def ListRemote(self):
+ ''' Lists the available overlays.
+ '''
+
+ self.output.debug('Printing remote overlays.', 2)
+ list_printer = ListPrinter(self.config)
+
+ _complain = self.config['nocheck'] or self.config['verbose']
+ info = self.api.get_info_list(local=False,
+ verbose=self.config['verbose'], width=list_printer.width)
+ list_printer.print_shortlist(info, complain=_complain)
+
+ return info != {}
+
+
+ def ListLocal(self):
+ ''' Lists the local overlays.
+ '''
+ #print "ListLocal()"
+ self.output.debug('Printing installed overlays.', 2)
+ list_printer = ListPrinter(self.config)
+
+ _complain = self.config['nocheck'] or self.config['verbose']
+ #
+ # fast way
+ info = self.api.get_info_list(verbose=self.config['verbose'],
+ width=list_printer.width)
+ list_printer.print_shortlist(info, complain=_complain)
+ #
+ # slow way
+ #info = self.api.get_all_info(self.api.get_installed(), local=True)
+ #list_printer.print_fulldict(info, complain=_complain)
+
+ return info != {}
+
+
+if __name__ == '__main__':
+ import doctest
+
+ # Ignore warnings here. We are just testing
+ from warnings import filterwarnings, resetwarnings
+ filterwarnings('ignore')
+
+ doctest.testmod(sys.modules[__name__])
+
+ resetwarnings()
diff --git a/overlord/config.py b/overlord/config.py
new file mode 100644
index 0000000..4caf83c
--- /dev/null
+++ b/overlord/config.py
@@ -0,0 +1,441 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord CONFIGURATION
+#################################################################################
+# File: config.py
+#
+# Handles overlord configuration
+#
+# Copyright:
+# (c) 2005 - 2009 Gunnar Wrobel
+# (c) 2009 Sebastian Pipping
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Sebastian Pipping <sebastian@pipping.org>
+# Brian Dolbec <brian.dolbec@gmail.com>
+#
+'''Defines the configuration options and provides parsing functionality.'''
+
+__version__ = "$Id: config.py 286 2007-01-09 17:48:23Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import sys, ConfigParser
+
+from optparse import OptionParser, OptionGroup
+from overlord.debug import OUT
+from overlord.version import VERSION
+
+#===============================================================================
+#
+# Class Config
+#
+#-------------------------------------------------------------------------------
+
+_USAGE = """
+ overlord (-a|-d|-s|-i) (OVERLAY|ALL)
+ overlord -f [-o URL]
+ overlord (-l|-L|-S)"""
+
+class BareConfig(object):
+ '''Handles the configuration only.'''
+
+ def __init__(self, output=None, stdout=None, stdin=None, stderr=None):
+ '''
+ Creates a bare config with defaults and a few output options.
+
+ >>> a = BareConfig()
+ >>> a['overlays']
+ '\\nhttp://www.gentoo.org/proj/en/overlays/repositories.xml'
+ >>> sorted(a.keys())
+ ['bzr_command', 'cache', 'config', 'cvs_command', 'darcs_command',
+ 'git_command', 'local_list', 'make_conf', 'mercurial_command',
+ 'nocheck', 'overlays', 'proxy', 'quietness', 'rsync_command', 'storage',
+ 'svn_command', 'tar_command', 'umask', 'width']
+ '''
+ self._defaults = {'config' : '/etc/overlord/overlord.cfg',
+ 'storage' : '/var/lib/layman',
+ 'cache' : '%(storage)s/cache',
+ 'local_list': '%(storage)s/overlays.xml',
+ 'make_conf' : '%(storage)s/make.conf',
+ 'nocheck' : 'yes',
+ 'proxy' : '',
+ 'umask' : '0022',
+ 'overlays' :
+ 'http://www.gentoo.org/proj/en/overlays/repositories.xml',
+ 'bzr_command': '/usr/bin/bzr',
+ 'cvs_command': '/usr/bin/cvs',
+ 'darcs_command': '/usr/bin/darcs',
+ 'git_command': '/usr/bin/git',
+ 'g-common_command': '/usr/bin/g-common',
+ 'mercurial_command': '/usr/bin/hg',
+ 'rsync_command': '/usr/bin/rsync',
+ 'svn_command': '/usr/bin/svn',
+ 'tar_command': '/bin/tar'
+ }
+ self._options = {
+ 'stdout': stdout if stdout else sys.stdout,
+ 'stdin': stdin if stdin else sys.stdin,
+ 'stderr': stderr if stderr else sys.stderr,
+ 'output': output if output else OUT,
+ 'quietness': '4',
+ 'width': 0,
+ 'verbose': False,
+ 'quiet': False,
+ }
+
+
+ def keys(self):
+ '''Special handler for the configuration keys.
+ '''
+ self._options['output'].debug('Retrieving BareConfig options', 8)
+ keys = [i for i in self._options]
+ self._options['output'].debug('Retrieving BareConfig defaults', 8)
+ keys += [i for i in self._defaults
+ if not i in keys]
+ self._options['output'].debug('Retrieving BareConfig done...', 8)
+ return keys
+
+
+ def get_defaults(self):
+ """returns our defaults dictionary"""
+ return self._defaults
+
+
+ def get_option(self, key):
+ """returns the current option's value"""
+ return self.__getitem__(key)
+
+
+ def set_option(self, option, value):
+ """Sets an option to the value """
+ self._options[option] = value
+
+
+ def __getitem__(self, key):
+ self._options['output'].debug('Retrieving BareConfig option', 8)
+ if (key in self._options
+ and not self._options[key] is None):
+ return self._options[key]
+ self._options['output'].debug('Retrieving BareConfig default', 8)
+ if key in self._defaults:
+ if '%(storage)s' in self._defaults[key]:
+ return self._defaults[key] %{'storage': self._defaults['storage']}
+ return self._defaults[key]
+ return None
+
+
+
+
+class ArgsParser(object):
+ '''Handles the configuration and option parser.'''
+
+ def __init__(self, args=None, output=None,
+ stdout=None, stdin=None, stderr=None):
+ '''
+ Creates and describes all possible polymeraZe options and creates
+ a debugging object.
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> sys.argv.append('--config')
+ >>> sys.argv.append(here + '/../etc/overlord.cfg')
+ >>> a = ArgsParser()
+ >>> a['overlays']
+ '\\nhttp://www.gentoo.org/proj/en/overlays/repositories.xml'
+ >>> sorted(a.keys())
+ ['bzr_command', 'cache', 'config', 'cvs_command', 'darcs_command',
+ 'git_command', 'local_list', 'make_conf', 'mercurial_command',
+ 'nocheck', 'overlays', 'proxy', 'quietness', 'rsync_command', 'storage',
+ 'svn_command', 'tar_command', 'umask', 'width']
+ '''
+ if args == None:
+ args = sys.argv
+
+ self.stdout = stdout if stdout else sys.stdout
+ self.stderr = stderr if stderr else sys.stderr
+ self.stdin = stdin if stdin else sys.stdin
+ self.output = output if output else OUT
+
+ self.bare_config = BareConfig(self.output, self.stdout,
+ self.stdin, self.stderr)
+ self.defaults = self.bare_config.get_defaults()
+ #print self.defaults
+
+ self.parser = OptionParser(
+ usage = _USAGE,
+ version = VERSION)
+
+ #-----------------------------------------------------------------
+ # Main Options
+
+ group = OptionGroup(self.parser,
+ '<Actions>')
+
+ group.add_option('-a',
+ '--add',
+ action = 'append',
+ help = 'Add the given overlay from the cached remote li'
+ 'st to your locally installed overlays.. Specify "ALL" '
+ 'to add all overlays from the remote list.')
+
+ group.add_option('-d',
+ '--delete',
+ action = 'append',
+ help = 'Remove the given overlay from your locally inst'
+ 'alled overlays. Specify "ALL" to remove all overlays')
+
+ group.add_option('-s',
+ '--sync',
+ action = 'append',
+ help = 'Update the specified overlay. Use "ALL" as para'
+ 'meter to synchronize all overlays')
+
+ group.add_option('-i',
+ '--info',
+ action = 'append',
+ help = 'Display information about the specified overlay'
+ '.')
+
+ group.add_option('-S',
+ '--sync-all',
+ action = 'store_true',
+ help = 'Update all overlays.')
+
+ group.add_option('-L',
+ '--list',
+ action = 'store_true',
+ help = 'List the contents of the remote list.')
+
+ group.add_option('-l',
+ '--list-local',
+ action = 'store_true',
+ help = 'List the locally installed overlays.')
+
+ group.add_option('-f',
+ '--fetch',
+ action = 'store_true',
+ help = 'Fetch a remote list of overlays. This option is'
+ ' deprecated. The fetch operation will be performed by '
+ 'default when you run sync, sync-all, or list.')
+
+ group.add_option('-n',
+ '--nofetch',
+ action = 'store_true',
+ help = 'Do not fetch a remote list of overlays.')
+
+ group.add_option('-p',
+ '--priority',
+ action = 'store',
+ help = 'Use this with the --add switch to set the prior'
+ 'ity of the added overlay. This will influence the sort'
+ 'ing order of the overlays in the PORTDIR_OVERLAY varia'
+ 'ble.')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Additional Options
+
+ group = OptionGroup(self.parser,
+ '<Path options>')
+
+ group.add_option('-c',
+ '--config',
+ action = 'store',
+ help = 'Path to the config file [default: ' \
+ + self.defaults['config'] + '].')
+
+ group.add_option('-o',
+ '--overlays',
+ action = 'append',
+ help = 'The list of overlays [default: ' \
+ + self.defaults['overlays'] + '].')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Output Options
+
+ group = OptionGroup(self.parser,
+ '<Output options>')
+
+ group.add_option('-v',
+ '--verbose',
+ action = 'store_true',
+ help = 'Increase the amount of output and describe the '
+ 'overlays.')
+
+ group.add_option('-q',
+ '--quiet',
+ action = 'store_true',
+ help = 'Yield no output. Please be careful with this op'
+ 'tion: If the processes spawned by overlord when adding o'
+ 'r synchronizing overlays require any input overlord will'
+ ' hang without telling you why. This might happen for e'
+ 'xample if your overlay resides in subversion and the S'
+ 'SL certificate of the server needs acceptance.')
+
+ group.add_option('-N',
+ '--nocolor',
+ action = 'store_true',
+ help = 'Remove color codes from the overlord output.')
+
+ group.add_option('-Q',
+ '--quietness',
+ action = 'store',
+ type = 'int',
+ default = '4',
+ help = 'Set the level of output (0-4). Default: 4. Once'
+ ' you set this below 2 the same warning as given for --'
+ 'quiet applies! ')
+
+ group.add_option('-W',
+ '--width',
+ action = 'store',
+ type = 'int',
+ default = '0',
+ help = 'Sets the screen width. This setting is usually '
+ 'not required as overlord is capable of detecting the '
+ 'available number of columns automatically.')
+
+ group.add_option('-k',
+ '--nocheck',
+ action = 'store_true',
+ help = 'Do not check overlay definitions and do not i'
+ 'ssue a warning if description or contact information'
+ ' are missing.')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Debug Options
+
+ self.output.cli_opts(self.parser)
+
+ # Parse the command line first since we need to get the config
+ # file option.
+ if len(args) == 1:
+ print 'Usage:%s' % _USAGE
+ sys.exit(0)
+
+ (self.options, remain_args) = self.parser.parse_args(args)
+ # remain_args starts with something like "bin/overlord" ...
+ if len(remain_args) > 1:
+ self.parser.error("Unhandled parameters: %s"
+ % ', '.join(('"%s"' % e) for e in remain_args[1:]))
+
+ # handle debugging
+ self.output.cli_handle(self.options)
+
+ # add output to the options
+ self.options.__dict__['output'] = self.output
+
+ # add the std in/out/err to the options
+ self.options.__dict__['stdout'] = self.stdout
+ self.options.__dict__['stdin'] = self.stdin
+ self.options.__dict__['stderr'] = self.stderr
+
+
+ if self.options.__dict__['nocolor']:
+ self.output.color_off()
+
+ # Fetch only an alternate config setting from the options
+ if not self.options.__dict__['config'] is None:
+ self.defaults['config'] = self.options.__dict__['config']
+
+ self.output.debug('Got config file at ' + self.defaults['config'], 8)
+
+ # Now parse the config file
+ self.config = ConfigParser.ConfigParser(self.defaults)
+ self.config.add_section('MAIN')
+
+ # handle quietness
+ if self['quiet']:
+ self.output.set_info_level(1)
+ self.output.set_warn_level(1)
+ self.defaults['quietness'] = 0
+ elif 'quietness' in self.keys():
+ self.output.set_info_level(int(self['quietness']))
+ self.output.set_warn_level(int(self['quietness']))
+
+ self.output.debug('Reading config file at ' + self.defaults['config'], 8)
+
+ self.config.read(self.defaults['config'])
+
+ def __getitem__(self, key):
+
+ if key == 'overlays':
+ overlays = ''
+ if (key in self.options.__dict__.keys()
+ and not self.options.__dict__[key] is None):
+ overlays = '\n'.join(self.options.__dict__[key])
+ if self.config.has_option('MAIN', 'overlays'):
+ overlays += '\n' + self.config.get('MAIN', 'overlays')
+ if overlays:
+ return overlays
+
+ self.output.debug('Retrieving option', 8)
+
+ if (key in self.options.__dict__.keys()
+ and not self.options.__dict__[key] is None):
+ return self.options.__dict__[key]
+
+ self.output.debug('Retrieving option', 8)
+
+ if self.config.has_option('MAIN', key):
+ if key == 'nocheck':
+ if self.config.get('MAIN', key).lower() == 'yes':
+ return True
+ else:
+ return False
+ return self.config.get('MAIN', key)
+
+ self.output.debug('Retrieving option', 8)
+
+ if key in self.defaults.keys():
+ return self.defaults[key]
+
+ self.output.debug('Retrieving option', 8)
+
+ return None
+
+ def keys(self):
+ '''Special handler for the configuration keys.'''
+
+ self.output.debug('Retrieving keys', 8)
+
+ keys = [i for i in self.options.__dict__.keys()
+ if not self.options.__dict__[i] is None]
+
+ self.output.debug('Retrieving keys', 8)
+
+ keys += [name for name, _ in self.config.items('MAIN')
+ if not name in keys]
+
+ self.output.debug('Retrieving keys', 8)
+
+ keys += [i for i in self.defaults.keys()
+ if not i in keys]
+
+ self.output.debug('Retrieving keys', 8)
+
+ return keys
+
+
+#===============================================================================
+#
+# Testing
+#
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(sys.modules[__name__])
diff --git a/overlord/constants.py b/overlord/constants.py
new file mode 100644
index 0000000..f89f286
--- /dev/null
+++ b/overlord/constants.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord CONSTANTS
+#################################################################################
+# File: constants.py
+#
+# Handles overlord actions via the command line interface.
+#
+# Copyright:
+# (c) 2010 - 2011
+# Gunnar Wrobel
+# Brian Dolbec
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Brian Dolbec <brian.dolbec@gmail.com
+#
+''' Provides the command line actions that can be performed by overlord.'''
+
+__version__ = "$Id: constants.py 2011-01-16 23:52 PST Brian Dolbec$"
+
+
+
+
+#################################################################################
+##
+## Color codes (taken from portage)
+##
+#################################################################################
+
+esc_seq = '\x1b['
+
+codes = {}
+codes['reset'] = esc_seq + '39;49;00m'
+codes['red'] = esc_seq + '31;01m'
+codes['green'] = esc_seq + '32;01m'
+codes['yellow'] = esc_seq + '33;01m'
+codes['turquoise'] = esc_seq + '36;01m'
+
+
+NOT_OFFICIAL_MSG = '*** This is not an official gentoo overlay ***\n'
+NOT_SUPPORTED_MSG = '*** You are lacking the necessary tools' +\
+ ' to install this overlay ***\n'
diff --git a/overlord/db.py b/overlord/db.py
new file mode 100644
index 0000000..d54efa5
--- /dev/null
+++ b/overlord/db.py
@@ -0,0 +1,353 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord OVERLAY DB
+#################################################################################
+# File: db.py
+#
+# Access to the db of overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+'''Handles different storage files.'''
+
+__version__ = "$Id: db.py 309 2007-04-09 16:23:38Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import os, os.path, urllib2, hashlib
+
+from overlord.utils import path, delete_empty_directory
+from overlord.dbbase import DbBase
+from overlord.makeconf import MakeConf
+
+#from overlord.debug import OUT
+
+#===============================================================================
+#
+# Class DB
+#
+#-------------------------------------------------------------------------------
+
+class DB(DbBase):
+ ''' Handle the list of local overlays.'''
+
+ def __init__(self, config):
+
+ self.config = config
+ self.output = config['output']
+
+ self.path = config['local_list']
+
+ if config['nocheck']:
+ ignore = 2
+ else:
+ ignore = 1
+
+ quiet = int(config['quietness']) < 3
+
+ DbBase.__init__(self,
+ [config['local_list'], ],
+ config,
+ ignore,
+ quiet)
+
+ self.output.debug('DB handler initiated', 6)
+
+ # overrider
+ def _broken_catalog_hint(self):
+ return ''
+
+ def add(self, overlay, quiet = False):
+ '''
+ Add an overlay to the local list of overlays.
+
+ >>> write = os.tmpnam()
+ >>> write2 = os.tmpnam()
+ >>> write3 = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : write2,
+ ... 'nocheck' : True,
+ ... 'storage' : write3,
+ ... 'quietness':3}
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = DB(config)
+ >>> config['local_list'] = write
+ >>> b = DB(config)
+ >>> OUT.color_off()
+
+ >>> m = MakeConf(config, b.overlays)
+ >>> m.path = write2
+ >>> m.write()
+
+ Commented out since it needs network access:
+
+ # >>> b.add(a.select('wrobel-stable')) #doctest: +ELLIPSIS
+ # * Running command "/usr/bin/rsync -rlptDvz --progress --delete --delete-after --timeout=180 --exclude="distfiles/*" --exclude="local/*" --exclude="packages/*" "rsync://gunnarwrobel.de/wrobel-stable/*" "/tmp/file.../wrobel-stable""...
+ # >>> c = DbBase([write, ], dict())
+ # >>> c.overlays.keys()
+ # [u'wrobel-stable']
+
+ # >>> m = MakeConf(config, b.overlays)
+ # >>> [i.name for i in m.overlays] #doctest: +ELLIPSIS
+ # [u'wrobel-stable']
+
+
+ # >>> os.unlink(write)
+ >>> os.unlink(write2)
+ >>> import shutil
+
+ # >>> shutil.rmtree(write3)
+ '''
+
+ if overlay.name not in self.overlays.keys():
+ result = overlay.add(self.config['storage'], quiet)
+ if result == 0:
+ if 'priority' in self.config.keys():
+ overlay.set_priority(self.config['priority'])
+ self.overlays[overlay.name] = overlay
+ self.write(self.path)
+ make_conf = MakeConf(self.config, self.overlays)
+ make_conf.add(overlay)
+ else:
+ mdir = path([self.config['storage'], overlay.name])
+ delete_empty_directory(mdir, self.output)
+ if os.path.exists(mdir):
+ raise Exception('Adding overlay "%s" failed!'
+ ' Possible remains of the operation have NOT'
+ ' been removed and may be left at "%s".'
+ ' Please remove them manually if required.' \
+ % (overlay.name, mdir))
+ else:
+ raise Exception('Adding overlay "%s" failed!' % overlay.name)
+ else:
+ raise Exception('Overlay "' + overlay.name + '" already in the loca'
+ 'l list!')
+
+ def delete(self, overlay):
+ '''
+ Add an overlay to the local list of overlays.
+
+ >>> write = os.tmpnam()
+ >>> write2 = os.tmpnam()
+ >>> write3 = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : write2,
+ ... 'nocheck' : True,
+ ... 'storage' : write3,
+ ... 'quietness':3}
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = DB(config)
+ >>> config['local_list'] = write
+ >>> b = DB(config)
+ >>> .color_off()
+
+ >>> m = MakeConf(config, b.overlays)
+ >>> m.path = here + '/tests/testfiles/make.conf'
+ >>> m.read()
+
+ >>> m.path = write2
+ >>> m.write()
+
+ # >>> b.add(a.select('wrobel-stable')) #doctest: +ELLIPSIS
+ # * Running command "/usr/bin/rsync -rlptDvz --progress --delete --delete-after --timeout=180 --exclude="distfiles/*" --exclude="local/*" --exclude="packages/*" "rsync://gunnarwrobel.de/wrobel-stable/*" "/tmp/file.../wrobel-stable""...
+ # >>> b.add(a.select('wrobel')) #doctest: +ELLIPSIS
+ # * Running command "/usr/bin/svn co "https://overlays.gentoo.org/svn/dev/wrobel/" "/tmp/file.../wrobel""...
+ # >>> c = DbBase([write, ], dict())
+ # >>> c.overlays.keys()
+ # [u'wrobel', u'wrobel-stable']
+
+ # >>> b.delete(b.select('wrobel'))
+ # >>> c = DbBase([write, ], dict())
+ # >>> c.overlays.keys()
+ # [u'wrobel-stable']
+
+ # >>> m = MakeConf(config, b.overlays)
+ # >>> [i.name for i in m.overlays] #doctest: +ELLIPSIS
+ # [u'wrobel-stable']
+
+ # >>> os.unlink(write)
+ >>> os.unlink(write2)
+ >>> import shutil
+
+ # >>> shutil.rmtree(write3)
+ '''
+
+ if overlay.name in self.overlays.keys():
+ make_conf = MakeConf(self.config, self.overlays)
+ overlay.delete(self.config['storage'])
+ del self.overlays[overlay.name]
+ self.write(self.path)
+ make_conf.delete(overlay)
+ else:
+ raise Exception('No local overlay named "' + overlay.name + '"!')
+
+ def sync(self, overlay_name, quiet = False):
+ '''Synchronize the given overlay.'''
+
+ overlay = self.select(overlay_name)
+ result = overlay.sync(self.config['storage'], quiet)
+ if result:
+ raise Exception('Syncing overlay "' + overlay_name +
+ '" returned status ' + str(result) + '!')
+
+#===============================================================================
+#
+# Class RemoteDB
+#
+#-------------------------------------------------------------------------------
+
+class RemoteDB(DbBase):
+ '''Handles fetching the remote overlay list.'''
+
+ def __init__(self, config, ignore_init_read_errors=False):
+
+ self.config = config
+ self.output = config['output']
+
+ self.proxies = {}
+
+ if config['proxy']:
+ self.proxies['http'] = config['proxy']
+ elif os.getenv('http_proxy'):
+ self.proxies['http'] = os.getenv('http_proxy')
+
+ if self.proxies:
+ proxy_handler = urllib2.ProxyHandler(self.proxies)
+ opener = urllib2.build_opener(proxy_handler)
+ urllib2.install_opener(opener)
+
+ self.urls = [i.strip() for i in config['overlays'].split('\n') if i]
+
+ paths = [self.path(i) for i in self.urls]
+
+ if config['nocheck']:
+ ignore = 2
+ else:
+ ignore = 0
+
+ quiet = int(config['quietness']) < 3
+
+ DbBase.__init__(self, paths, config, ignore, quiet, ignore_init_read_errors)
+
+ # overrider
+ def _broken_catalog_hint(self):
+ return 'Try running "sudo overlord -f" to re-fetch that file'
+
+ def cache(self):
+ '''
+ Copy the remote overlay list to the local cache.
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> cache = os.tmpnam()
+ >>> config = {'overlays' :
+ ... 'file://' + here + '/tests/testfiles/global-overlays.xml',
+ ... 'cache' : cache,
+ ... 'nocheck' : True,
+ ... 'proxy' : None,
+ ... 'quietness':3}
+ >>> a = RemoteDB(config)
+ >>> a.cache()
+ >>> b = open(a.path(config['overlays']))
+ >>> b.readlines()[24]
+ ' A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org].\\n'
+
+ >>> b.close()
+ >>> os.unlink(a.path(config['overlays']))
+
+ >>> a.overlays.keys()
+ [u'wrobel', u'wrobel-stable']
+ '''
+ for url in self.urls:
+
+ mpath = self.path(url)
+
+ # Check for sufficient privileges
+ if os.path.exists(mpath) and not os.access(mpath, os.W_OK):
+ self.output.warn('You do not have permission to update the cache (%s).' % mpath)
+ import getpass
+ if getpass.getuser() != 'root':
+ self.output.warn('Hint: You are not root.\n')
+ continue
+
+ try:
+
+ # Fetch the remote list
+ olist = urllib2.urlopen(url).read()
+
+ # Create our storage directory if it is missing
+ if not os.path.exists(os.path.dirname(mpath)):
+ try:
+ os.makedirs(os.path.dirname(mpath))
+ except OSError, error:
+ raise OSError('Failed to create overlord storage direct'
+ + 'ory ' + os.path.dirname(mpath) + '\n'
+ + 'Error was:' + str(error))
+
+ # Before we overwrite the old cache, check that the downloaded
+ # file is intact and can be parsed
+ try:
+ self.read(olist, origin=url)
+ except Exception, error:
+ raise IOError('Failed to parse the overlays list fetched fr'
+ 'om ' + url + '\nThis means that the download'
+ 'ed file is somehow corrupt or there was a pr'
+ 'oblem with the webserver. Check the content '
+ 'of the file. Error was:\n' + str(error))
+
+ # Ok, now we can overwrite the old cache
+ try:
+ out_file = open(mpath, 'w')
+ out_file.write(olist)
+ out_file.close()
+
+ except Exception, error:
+ raise IOError('Failed to temporarily cache overlays list in'
+ ' ' + mpath + '\nError was:\n' + str(error))
+
+
+ except IOError, error:
+ self.output.warn('Failed to update the overlay list from: '
+ + url + '\nError was:\n' + str(error))
+
+ def path(self, url):
+ '''Return a unique file name for the url.'''
+
+ base = self.config['cache']
+
+ self.output.debug('Generating cache path.', 6)
+
+ return base + '_' + hashlib.md5(url).hexdigest() + '.xml'
+
+
+#===============================================================================
+#
+# Testing
+#
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import doctest, sys
+
+ # Ignore warnings here. We are just testing
+ from warnings import filterwarnings, resetwarnings
+ filterwarnings('ignore')
+
+ doctest.testmod(sys.modules[__name__])
+
+ resetwarnings()
diff --git a/overlord/dbbase.py b/overlord/dbbase.py
new file mode 100644
index 0000000..7821aea
--- /dev/null
+++ b/overlord/dbbase.py
@@ -0,0 +1,276 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord OVERLAY HANDLER
+#################################################################################
+# File: overlay.py
+#
+# Access to an xml list of overlays
+#
+# Copyright:
+# (c) 2005 - 2009 Gunnar Wrobel
+# (c) 2009 Sebastian Pipping
+# (c) 2009 Christian Groschupp
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Sebastian Pipping <sebastian@pipping.org>
+# Christian Groschupp <christian@groschupp.org>
+#
+'''Main handler for overlays.'''
+
+__version__ = "$Id: overlay.py 273 2006-12-30 15:54:50Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import sys, os, os.path
+import xml
+import xml.etree.ElementTree as ET # Python 2.5
+
+#from overlord.debug import OUT
+from overlord.utils import indent
+from overlord.overlays.overlay import Overlay
+
+#===============================================================================
+#
+# Class UnknownOverlayException
+#
+#-------------------------------------------------------------------------------
+
+class UnknownOverlayException(Exception):
+ def __init__(self, repo_name):
+ message = 'Overlay "%s" does not exist.' % repo_name
+ super(UnknownOverlayException, self).__init__(message)
+
+#===============================================================================
+#
+# Class BrokenOverlayCatalog
+#
+#-------------------------------------------------------------------------------
+
+class BrokenOverlayCatalog(ValueError):
+ def __init__(self, origin, expat_error, hint=None):
+ if hint == None:
+ hint = ''
+ else:
+ hint = '\nHint: %s' % hint
+
+ super(BrokenOverlayCatalog, self).__init__(
+ 'XML parsing failed for "%(origin)s" (line %(line)d, column %(column)d)%(hint)s' % \
+ {'line':expat_error.lineno, 'column':expat_error.offset + 1, 'origin':origin, 'hint':hint})
+
+
+#===============================================================================
+#
+# Class DbBase
+#
+#-------------------------------------------------------------------------------
+
+class DbBase:
+ ''' Handle a list of overlays.'''
+
+ def __init__(self, paths, config, ignore = 0, quiet = False, ignore_init_read_errors=False):
+
+ self.config = config
+ self.quiet = quiet
+ self.paths = paths
+ self.ignore = ignore
+ self.output = config['output']
+
+ self.overlays = {}
+
+ self.output.debug('Initializing overlay list handler', 8)
+
+ for path in self.paths:
+ if not os.path.exists(path):
+ continue
+
+ try:
+ self.read_file(path)
+ except Exception, error:
+ if not ignore_init_read_errors: raise error
+
+ def __eq__(self, other):
+ for key in set(self.overlays.keys() + other.overlays.keys()):
+ if self.overlays[key] != other.overlays[key]:
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def read_file(self, path):
+ '''Read the overlay definition file.'''
+
+ try:
+ document = open(path, 'r').read()
+
+ except Exception, error:
+ raise IOError('Failed to read the overlay list at ("'
+ + path + '")!\nError was:\n' + str(error))
+
+ self.read(document, origin=path)
+
+ def _broken_catalog_hint(self):
+ this_function_name = sys._getframe().f_code.co_name
+ raise NotImplementedError('Method "%s.%s" not implemented' % \
+ (self.__class__.__name__, this_function_name))
+
+ def read(self, text, origin):
+ '''
+ Read an xml list of overlays (adding to and potentially overwriting existing entries)
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'svn_command': '/usr/bin/svn', 'rsync_command':'/usr/bin/rsync'}
+ >>> a = DbBase([here + '/tests/testfiles/global-overlays.xml', ], config)
+ >>> a.overlays.keys()
+ [u'wrobel', u'wrobel-stable']
+
+ >>> list(a.overlays['wrobel-stable'].source_uris())
+ [u'rsync://gunnarwrobel.de/wrobel-stable']
+ '''
+ try:
+ document = ET.fromstring(text)
+ except xml.parsers.expat.ExpatError, error:
+ raise BrokenOverlayCatalog(origin, error, self._broken_catalog_hint())
+
+ overlays = document.findall('overlay') + \
+ document.findall('repo')
+
+ for overlay in overlays:
+ self.output.debug('Parsing overlay entry', 8)
+ try:
+ ovl = Overlay(overlay, self.config, self.ignore, self.quiet)
+ except Exception, error:
+ self.output.warn(str(error), 3)
+ else:
+ self.overlays[ovl.name] = ovl
+
+
+ def write(self, path):
+ '''
+ Write the list of overlays to a file.
+
+ >>> write = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'svn_command': '/usr/bin/svn', 'rsync_command':'/usr/bin/rsync'}
+ >>> a = DbBase([here + '/tests/testfiles/global-overlays.xml', ], config)
+ >>> b = DbBase([write,], dict())
+ >>> b.overlays['wrobel-stable'] = a.overlays['wrobel-stable']
+ >>> b.write(write)
+ >>> c = DbBase([write,], dict())
+ >>> c.overlays.keys()
+ [u'wrobel-stable']
+
+ >>> os.unlink(write)
+ '''
+
+ tree = ET.Element('repositories', version="1.0")
+ tree[:] = [e.to_xml() for e in self.overlays.values()]
+ indent(tree)
+ tree = ET.ElementTree(tree)
+ try:
+ f = open(path, 'w')
+ f.write("""\
+<?xml version="1.0" encoding="UTF-8"?>
+""")
+ tree.write(f, encoding='utf-8')
+ f.close()
+ except Exception, error:
+ raise Exception('Failed to write to local overlays file: '
+ + path + '\nError was:\n' + str(error))
+
+ def select(self, overlay):
+ '''
+ Select an overlay from the list.
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'svn_command': '/usr/bin/svn', 'rsync_command':'/usr/bin/rsync'}
+ >>> a = DbBase([here + '/tests/testfiles/global-overlays.xml', ], config)
+ >>> list(a.select('wrobel-stable').source_uris())
+ [u'rsync://gunnarwrobel.de/wrobel-stable']
+ '''
+ if not overlay in self.overlays.keys():
+ raise UnknownOverlayException(overlay)
+ return self.overlays[overlay]
+
+ def list(self, repos=None, verbose = False, width = 0):
+ '''
+ List all overlays.
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'svn_command': '/usr/bin/svn', 'rsync_command':'/usr/bin/rsync'}
+ >>> a = DbBase([here + '/tests/testfiles/global-overlays.xml', ], config)
+ >>> for i in a.list(True):
+ ... print i[0]
+ wrobel
+ ~~~~~~
+ Source : https://overlays.gentoo.org/svn/dev/wrobel
+ Contact : nobody@gentoo.org
+ Type : Subversion; Priority: 10
+ Quality : experimental
+ <BLANKLINE>
+ Description:
+ Test
+ <BLANKLINE>
+ wrobel-stable
+ ~~~~~~~~~~~~~
+ Source : rsync://gunnarwrobel.de/wrobel-stable
+ Contact : nobody@gentoo.org
+ Type : Rsync; Priority: 50
+ Quality : experimental
+ <BLANKLINE>
+ Description:
+ A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org].
+ <BLANKLINE>
+
+ >>> for i in a.list(False, 80):
+ ... print i[0]
+ wrobel [Subversion] (https://o.g.o/svn/dev/wrobel )
+ wrobel-stable [Rsync ] (rsync://gunnarwrobel.de/wrobel-stable)
+ '''
+ result = []
+
+ selection = [overlay for (a, overlay) in self.overlays.items()]
+ if repos:
+ selection = [overlay for overlay in selection if overlay.name in repos]
+
+ for overlay in selection:
+ if verbose:
+ result.append((str(overlay), overlay.is_supported(),
+ overlay.is_official()))
+ else:
+ result.append((overlay.short_list(width), overlay.is_supported(),
+ overlay.is_official()))
+
+ result = sorted(result)
+
+ return result
+
+ def list_ids(self):
+ """returns a list of the overlay names
+ """
+ return sorted(self.overlays)
+
+
+#===============================================================================
+#
+# Testing
+#
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import doctest
+
+ # Ignore warnings here. We are just testing
+ from warnings import filterwarnings, resetwarnings
+ filterwarnings('ignore')
+
+ doctest.testmod(sys.modules[__name__])
+
+ resetwarnings()
diff --git a/overlord/debug.py b/overlord/debug.py
new file mode 100644
index 0000000..eca6266
--- /dev/null
+++ b/overlord/debug.py
@@ -0,0 +1,515 @@
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord - DEBUGGING FUNCTIONS
+#################################################################################
+# debug.py -- Utility function for debugging
+# Copyright 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+
+__version__ = "$Id: debug.py 153 2006-06-05 06:03:16Z wrobel $"
+
+#################################################################################
+##
+## Dependancies
+##
+#################################################################################
+
+import sys, inspect, types
+
+from optparse import OptionGroup
+
+from overlord.constants import codes
+
+
+#################################################################################
+##
+## Message Class
+##
+#################################################################################
+
+class Message:
+ #FIXME: Think about some simple doctests before you modify this class the
+ # next time.
+
+ def __init__(self, module = '',
+ out = sys.stdout,
+ err = sys.stderr,
+ dbg = sys.stderr,
+ debugging_level = 4,
+ debugging_verbosity = 2,
+ info_level = 4,
+ warn_level = 4,
+ col = True,
+ mth = None,
+ obj = None,
+ var = None):
+
+ if mth == None: mth = ['*']
+ if obj == None: obj = ['*']
+ if var == None: var = ['*']
+
+ # A description of the module that is being debugged
+ self.debug_env = module
+
+ # Where should the debugging output go? This can also be a file
+ self.debug_out = dbg
+
+ # Where should the error output go? This can also be a file
+ self.error_out = err
+
+ # Where should the normal output go? This can also be a file
+ self.std_out = out
+
+ # The higher the level the more information you will get
+ self.warn_lev = warn_level
+
+ # The higher the level the more information you will get
+ self.info_lev = info_level
+
+ # The highest level of debugging messages acceptable for output
+ # The higher the level the more output you will get
+ self.debug_lev = debugging_level
+
+ # The debugging output can range from very verbose (3) to
+ # very compressed (1)
+ self.debug_vrb = debugging_verbosity
+
+ # Which methods should actually be debugged?
+ # Use '*' to indicate 'All methods'
+ self.debug_mth = mth
+
+ # Which objects should actually be debugged?
+ # Use '*' to indicate 'All objects'
+ self.debug_obj = obj
+
+ # Which variables should actually be debugged?
+ # Use '*' to indicate 'All variables'
+ self.debug_var = var
+
+ # Exclude class variables by default
+ self.show_class_variables = False
+
+ # Should the output be colored?
+ self.use_color = col
+
+ self.has_error = False
+
+
+ ############################################################################
+ # Add command line options
+
+ def cli_opts(self, parser):
+
+ #print "Parsing debug opts"
+
+ group = OptionGroup(parser,
+ '<Debugging options>',
+ 'Control the debugging features of '
+ + self.debug_env)
+
+ group.add_option('--debug',
+ action = 'store_true',
+ help = 'Activates debugging features.')
+
+ group.add_option('--debug-level',
+ action = 'store',
+ type = 'int',
+ help = 'A value between 0 and 10. 0 means no debugging '
+ 'messages will be selected, 10 selects all debugging me'
+ 'ssages. Default is "4".')
+
+ group.add_option('--debug-verbose',
+ action = 'store',
+ type = 'int',
+ help = 'A value between 1 and 3. Lower values yield les'
+ 's verbose debugging output. Default is "2".')
+
+ group.add_option('--debug-methods',
+ action = 'store',
+ help = 'Limits the methods that will return debugging o'
+ 'utput. The function name is sufficient and there is no'
+ 'difference between class methods or general functions.'
+ ' Several methods can be specified by seperating them w'
+ ' with a comma. Default is "*" which specifies all meth'
+ 'ods.')
+
+ group.add_option('--debug-classes',
+ action = 'store',
+ help = 'Limits the classes that will return debugging o'
+ 'utput. Specify only the class name not including the m'
+ 'odules in which the class is defined (e.g. MyModule.ma'
+ 'in.Main should only be represented by "Main"). Several'
+ 'classes can be specified by seperating them with a com'
+ 'ma. Default is "*" which specifies all classes.')
+
+ group.add_option('--debug-variables',
+ action = 'store',
+ help = 'Limits the variables that will return debugging'
+ ' output. Several variables can be specified by seperat'
+ 'ing them with a comma. Default is "*" which specifies '
+ 'all variables.')
+
+ group.add_option('--debug-class-vars',
+ action = 'store_true',
+ help = 'In default mode the debugging code will only re'
+ 'turn information on the local variable which does not '
+ 'include the class variables. Use this switch to add al'
+ 'l values that are provided by "self".')
+
+ group.add_option('--debug-nocolor',
+ action = 'store_true',
+ help = 'Deactivates colors in the debugging output.')
+
+ parser.add_option_group(group)
+
+
+ #############################################################################
+ # Handle command line options
+
+ def cli_handle(self, options):
+
+ if (options.__dict__.has_key('debug')
+ and options.__dict__['debug']):
+ self.debug_on()
+ else:
+ self.debug_off()
+ return
+
+ if (options.__dict__.has_key('debug_class_vars')
+ and options.__dict__['debug_class_vars']):
+ self.class_variables_on()
+ else:
+ self.class_variables_off()
+
+ if (options.__dict__.has_key('debug_nocolor')
+ and options.__dict__['debug_nocolor']):
+ self.color_off()
+ else:
+ self.color_on()
+
+ if (options.__dict__.has_key('debug_level') and
+ options.__dict__['debug_level']):
+ dbglvl = int(options.__dict__['debug_level'])
+ if dbglvl < 0:
+ dbglvl = 0
+ if dbglvl > 10:
+ dbglvl = 10
+ self.set_debug_level(dbglvl)
+
+ if (options.__dict__.has_key('debug_verbose') and
+ options.__dict__['debug_verbose']):
+ dbgvrb = int(options.__dict__['debug_verbose'])
+ if dbgvrb < 1:
+ dbgvrb = 1
+ if dbgvrb > 3:
+ dbgvrb = 3
+ self.set_debug_verbosity(dbgvrb)
+
+ for i in [('debug_methods', self.set_debug_methods),
+ ('debug_classes', self.set_debug_classes),
+ ('debug_variables', self.set_debug_variables),]:
+
+ if (options.__dict__.has_key(i[0]) and
+ options.__dict__[i[0]]):
+ i[1](options.__dict__[i[0]])
+
+
+ #############################################################################
+ ## Helper Functions
+
+ def set_module(self, module):
+
+ self.debug_env = module
+
+ def set_debug_methods(self, methods):
+
+ methods = methods.split(',')
+
+ if methods:
+ self.debug_mth = methods
+
+ def set_debug_classes(self, classes):
+
+ classes = classes.split(',')
+
+ if classes:
+ self.debug_obj = classes
+
+ def set_debug_variables(self, variables):
+
+ variables = variables.split(',')
+
+ if variables:
+ self.debug_var = variables
+
+ def maybe_color (self, col, text):
+ if self.use_color:
+ return codes[col] + text + codes['reset']
+ return text
+
+ def set_info_level(self, info_level = 4):
+ self.info_lev = info_level
+
+ def info_off(self):
+ self.set_info_level(0)
+
+ def info_on(self, info_level = 4):
+ self.set_info_level(info_level)
+
+ def set_warn_level(self, warn_level = 4):
+ self.warn_lev = warn_level
+
+ def warn_off(self):
+ self.set_warn_level(0)
+
+ def warn_on(self, warn_level = 4):
+ self.set_warn_level(warn_level)
+
+ def set_debug_level(self, debugging_level = 4):
+ self.debug_lev = debugging_level
+
+ def set_debug_verbosity(self, debugging_verbosity = 2):
+ self.debug_vrb = debugging_verbosity
+
+ def debug_off(self):
+ self.set_debug_level(0)
+
+ def debug_on(self):
+ self.set_debug_level()
+
+ def color_off(self):
+ self.use_color = False
+
+ def color_on(self):
+ self.use_color = True
+
+ def class_variables_off(self):
+ self.show_class_variables = False
+
+ def class_variables_on(self):
+ self.show_class_variables = True
+
+ #############################################################################
+ ## Output Functions
+
+ def notice (self, note):
+ print >> self.std_out, note
+
+ def info (self, info, level = 4):
+
+ #print "info =", info
+
+ if type(info) not in types.StringTypes:
+ info = str(info)
+
+ if level > self.info_lev:
+ return
+
+ for i in info.split('\n'):
+ print >> self.std_out, self.maybe_color('green', '* ') + i
+
+ def status (self, message, status, info = 'ignored'):
+
+ if type(message) not in types.StringTypes:
+ message = str(message)
+
+ lines = message.split('\n')
+
+ if not lines:
+ return
+
+ for i in lines[0:-1]:
+ print >> self.std_out, self.maybe_color('green', '* ') + i
+
+ i = lines[-1]
+
+ if len(i) > 58:
+ i = i[0:57]
+
+ if status == 1:
+ result = '[' + self.maybe_color('green', 'ok') + ']'
+ elif status == 0:
+ result = '[' + self.maybe_color('red', 'failed') + ']'
+ else:
+ result = '[' + self.maybe_color('yellow', info) + ']'
+
+ print >> self.std_out, self.maybe_color('green', '* ') + i + ' ' + '.' * (58 - len(i)) \
+ + ' ' + result
+
+ def warn (self, warn, level = 4):
+
+ #print "DEBUG.warn()"
+
+ if type(warn) not in types.StringTypes:
+ warn = str(warn)
+
+ if level > self.warn_lev:
+ return
+
+ for i in warn.split('\n'):
+ print >> self.std_out, self.maybe_color('yellow', '* ') + i
+
+ def error (self, error):
+
+ if type(error) not in types.StringTypes:
+ error = str(error)
+
+ for i in error.split('\n'):
+ # NOTE: Forced flushing ensures that stdout and stderr
+ # stay in nice order. This is a workaround for calls like
+ # "overlord -L |& less".
+ sys.stdout.flush()
+ print >> self.error_out, self.maybe_color('red', '* ') + i
+ self.error_out.flush()
+ self.has_error = True
+
+ def die (self, error):
+
+ if type(error) not in types.StringTypes:
+ error = str(error)
+
+ for i in error.split('\n'):
+ self.error(self.maybe_color('red', 'Fatal error: ') + i)
+ self.error(self.maybe_color('red', 'Fatal error(s) - aborting'))
+ sys.exit(1)
+
+ def debug (self, message, level = 4):
+ '''
+ This is a generic debugging method.
+ '''
+ ## Check the debug level first. This is the most inexpensive check.
+ if level > self.debug_lev:
+ return
+
+ ## Maybe this should be debugged. So get the stack first.
+ stack = inspect.stack()
+
+ ## This can probably never happen but does not harm to check
+ ## that there is actually something calling this function
+ if len(stack) < 2:
+ return
+
+ ## Get the stack length to determine indentation of the debugging output
+ stacklength = len(stack)
+ ls = ' ' * stacklength
+
+ ## Get the information about the caller
+ caller = stack[1]
+
+ ## The function name of the calling frame is the fourth item in the list
+ callermethod = caller[3]
+
+ ## Is this actually one of the methods that should be debugged?
+ if not '*' in self.debug_mth and not callermethod in self.debug_mth:
+ return
+
+ ## Still looks like this should be debugged. So retrieve the dictionary
+ ## of local variables from the caller
+ callerlocals = inspect.getargvalues(caller[0])[3]
+
+ ## Is the caller an obejct? If so he provides 'self'
+ if 'self' in callerlocals.keys():
+ callerobject = callerlocals['self']
+ del callerlocals['self']
+ if self.show_class_variables:
+ cv = inspect.getmembers(callerobject,
+ lambda x: not inspect.ismethod(x))
+ callerlocals.sync(cv)
+ else:
+ callerobject = None
+
+ # Remove variables not requested
+ if not '*' in self.debug_var:
+ callerlocals = dict([i for i in callerlocals.items()
+ if i[0] in self.debug_var])
+
+ ## Is the object among the list of objects to debug?
+ if (not '*' in self.debug_obj and
+ not str(callerobject.__class__.__name__) in self.debug_obj):
+ return
+
+ if type(message) not in types.StringTypes:
+ message = str(message)
+
+ def breaklines(x):
+ '''
+ Helper function to keep width of the debugging output.
+
+ This may look ugly for arrays but it is acceptable and not
+ breaking the line would break the output format
+ '''
+ ## Get the number of lines we need (rounded down)
+ lines = len(x) // 60
+ if lines > 0:
+ for j in range(lines):
+ ## Print line with continuation marker
+ print >> self.debug_out, ls + '// ' + x[0:60] + ' \\'
+ ## Remove printed characters from output
+ x = x[60:]
+ ## Print final line
+ print >> self.debug_out, ls + '// ' + x
+
+ if self.debug_vrb == 1:
+ # Top line indicates class and method
+ c = ''
+ if callerobject:
+ c += 'Class: ' + str(callerobject.__class__.__name__) + ' | '
+ if callermethod:
+ c += 'Method: ' + str(callermethod)
+ print >> self.debug_out, '// ' + c
+ # Selected variables follow
+ if callerlocals:
+ for i,j in callerlocals.items():
+ print >> self.debug_out, '// ' \
+ + self.maybe_color('turquoise', str(i)) + ':' + str(j)
+ # Finally the message
+ print >> self.debug_out, self.maybe_color('yellow', message)
+ return
+
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '/////////////////////////////////' + \
+ '////////////////////////////////'
+
+ # General information about what is being debugged
+ #(module name or similar)
+ print >> self.debug_out, ls + '// ' + self.debug_env
+ print >> self.debug_out, ls + '//-----------------------------------' + \
+ '----------------------------'
+
+ ## If the caller is a class print the name here
+ if callerobject:
+ print >> self.debug_out, ls + \
+ '// Object Class: ' + str(callerobject.__class__.__name__)
+
+ ## If the method has been extracted print it here
+ if callermethod:
+ print >> self.debug_out, ls + '// ' \
+ + self.maybe_color('green', 'Method: ') + str(callermethod)
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//---------------------------' + \
+ '------------------------------------'
+
+ ## Print the information on all available local variables
+ if callerlocals:
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//'
+ print >> self.debug_out, ls + '// VALUES '
+ for i,j in callerlocals.items():
+ print >> self.debug_out, ls + '// ------------------> ' \
+ + self.maybe_color('turquoise', str(i)) + ':'
+ breaklines(str(j))
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//------------------------------'\
+ '---------------------------------'
+
+ # Finally print the message
+ breaklines(self.maybe_color('yellow', message))
+
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//-------------------------------' + \
+ '--------------------------------'
+ print >> self.debug_out, ls + '/////////////////////////////////' + \
+ '////////////////////////////////'
+
+## gloabal message handler
+OUT = Message('overlord')
diff --git a/overlord/makeconf.py b/overlord/makeconf.py
new file mode 100644
index 0000000..4906d0a
--- /dev/null
+++ b/overlord/makeconf.py
@@ -0,0 +1,269 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# MAKE-DOT-CONF HANDLING
+#################################################################################
+# File: makeconf.py
+#
+# Handles modifications to /etc/make.conf
+#
+# Copyright:
+# (c) 2005 - 2009 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+
+import os
+import codecs
+import re
+
+from overlord.utils import path
+
+#===============================================================================
+#
+# Helper class MakeConf
+#
+#-------------------------------------------------------------------------------
+
+class MakeConf:
+ '''
+ Handles modifications to /etc/make.conf
+
+ Check that an add/remove cycle does not modify the make.conf:
+
+ >>> import hashlib
+ >>> write = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : here + '/tests/testfiles/make.conf',
+ ... 'nocheck' : True,
+ ... 'storage' : '/var/lib/layman',
+ ... 'quietness':3}
+ >>> b = DB(config)
+ >>> a = MakeConf(config, b.overlays)
+ >>> o_md5 = str(hashlib.md5(open(here + '/tests/testfiles/make.conf').read()).hexdigest())
+ >>> a.path = write
+ >>> a.add(b.overlays['wrobel-stable'])
+ >>> [i.name for i in a.overlays]
+ [u'wrobel-stable', u'wrobel-stable']
+ >>> a.add(b.overlays['wrobel'])
+ >>> [i.name for i in a.overlays]
+ [u'wrobel', u'wrobel-stable', u'wrobel-stable']
+ >>> a.delete(b.overlays['wrobel-stable'])
+ >>> [i.name for i in a.overlays]
+ [u'wrobel']
+ >>> a.add(b.overlays['wrobel-stable'])
+ >>> [i.name for i in a.overlays]
+ [u'wrobel', u'wrobel-stable']
+ >>> a.delete(b.overlays['wrobel'])
+ >>> n_md5 = str(hashlib.md5(open(write).read()).hexdigest())
+ >>> o_md5 == n_md5
+ True
+ >>> os.unlink(write)
+ '''
+
+ my_re = re.compile('PORTDIR_OVERLAY\s*=\s*"([^"]*)"')
+
+ def __init__(self, config, overlays):
+
+ self.path = config['make_conf']
+ self.storage = config['storage']
+ self.data = ''
+ self.db = overlays
+ self.overlays = []
+ self.extra = []
+
+ self.read()
+
+ def add(self, overlay):
+ '''
+ Add an overlay to make.conf.
+
+ >>> write = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : here + '/tests/testfiles/make.conf',
+ ... 'nocheck' : True,
+ ... 'storage' : '/var/lib/layman',
+ ... 'quietness':3}
+ >>> c = DB(config)
+ >>> a = MakeConf(config, c.overlays)
+ >>> a.path = write
+ >>> a.add(c.select('wrobel'))
+ >>> config['make_conf'] = write
+ >>> b = MakeConf(config, c.overlays)
+ >>> [i.name for i in b.overlays]
+ [u'wrobel', u'wrobel-stable']
+ >>> b.extra
+ [u'/usr/local/portage/ebuilds/testing', u'/usr/local/portage/ebuilds/stable', u'/usr/local/portage/kolab2', u'/usr/local/portage/gentoo-webapps-overlay/experimental', u'/usr/local/portage/gentoo-webapps-overlay/production-ready']
+
+ >>> os.unlink(write)
+ '''
+ self.overlays.append(overlay)
+ self.write()
+
+ def delete(self, overlay):
+ '''
+ Delete an overlay from make.conf.
+
+ >>> write = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : here + '/tests/testfiles/make.conf',
+ ... 'nocheck' : True,
+ ... 'storage' : '/var/lib/layman',
+ ... 'quietness':3}
+ >>> c = DB(config)
+ >>> a = MakeConf(config, c.overlays)
+ >>> a.path = write
+ >>> a.delete(c.select('wrobel-stable'))
+ >>> config['make_conf'] = write
+ >>> b = MakeConf(config, c.overlays)
+ >>> [i.name for i in b.overlays]
+ []
+ >>> b.extra
+ [u'/usr/local/portage/ebuilds/testing', u'/usr/local/portage/ebuilds/stable', u'/usr/local/portage/kolab2', u'/usr/local/portage/gentoo-webapps-overlay/experimental', u'/usr/local/portage/gentoo-webapps-overlay/production-ready']
+
+ >>> os.unlink(write)
+ '''
+ self.overlays = [i
+ for i in self.overlays
+ if i.name != overlay.name]
+ self.write()
+
+ def read(self):
+ '''
+ Read the list of registered overlays from /etc/make.conf.
+
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : here + '/tests/testfiles/make.conf',
+ ... 'nocheck' : True,
+ ... 'storage' : '/var/lib/layman',
+ ... 'quietness':3}
+ >>> c = DB(config)
+ >>> a = MakeConf(config, c.overlays)
+ >>> [i.name for i in a.overlays]
+ [u'wrobel-stable']
+ >>> a.extra
+ [u'/usr/local/portage/ebuilds/testing', u'/usr/local/portage/ebuilds/stable', u'/usr/local/portage/kolab2', u'/usr/local/portage/gentoo-webapps-overlay/experimental', u'/usr/local/portage/gentoo-webapps-overlay/production-ready']
+ '''
+ if os.path.isfile(self.path):
+ self.content()
+
+ overlays = self.my_re.search(self.data)
+
+ if not overlays:
+ raise Exception('Did not find a PORTDIR_OVERLAY entry in file ' +
+ self.path +'! Did you specify the correct file?')
+
+ overlays = [i.strip()
+ for i in overlays.group(1).split('\n')
+ if i.strip()]
+
+ for i in overlays:
+ if i[:len(self.storage)] == self.storage:
+ oname = os.path.basename(i)
+ if oname in self.db.keys():
+ self.overlays.append(self.db[oname])
+ else:
+ # These are additional overlays that we dont know
+ # anything about. The user probably added them manually
+ self.extra.append(i)
+ else:
+ # These are additional overlays that we dont know anything
+ # about. The user probably added them manually
+ self.extra.append(i)
+
+
+ else:
+ self.overlays = []
+ self.data = 'PORTDIR_OVERLAY="\n"\n'
+
+ self.extra = [i for i in self.extra
+ if (i != '$PORTDIR_OVERLAY'
+ and i != '${PORTDIR_OVERLAY}')]
+
+ def write(self):
+ '''
+ Write the list of registered overlays to /etc/make.conf.
+
+ >>> write = os.tmpnam()
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config = {'local_list' :
+ ... here + '/tests/testfiles/global-overlays.xml',
+ ... 'make_conf' : here + '/tests/testfiles/make.conf',
+ ... 'nocheck' : True,
+ ... 'storage' : '/var/lib/layman',
+ ... 'quietness':3}
+ >>> c = DB(config)
+ >>> a = MakeConf(config, c.overlays)
+ >>> a.path = write
+ >>> a.write()
+ >>> config['make_conf'] = write
+ >>> b = MakeConf(config, c.overlays)
+ >>> [i.name for i in b.overlays]
+ [u'wrobel-stable']
+ >>> b.extra
+ [u'/usr/local/portage/ebuilds/testing', u'/usr/local/portage/ebuilds/stable', u'/usr/local/portage/kolab2', u'/usr/local/portage/gentoo-webapps-overlay/experimental', u'/usr/local/portage/gentoo-webapps-overlay/production-ready']
+
+ >>> os.unlink(write)
+ '''
+ def prio_sort(a, b):
+ '''Sort by priority.'''
+ if a.priority < b.priority:
+ return -1
+ elif a.priority > b.priority:
+ return 1
+ return 0
+
+ self.overlays.sort(prio_sort)
+
+ paths = []
+ for i in self.overlays:
+ paths.append(path((self.storage, i.name, )))
+
+ overlays = 'PORTDIR_OVERLAY="\n'
+ overlays += '\n'.join(paths) + '\n'
+ overlays += '$PORTDIR_OVERLAY\n'
+ overlays += '\n'.join(self.extra)
+ overlays += '"'
+
+ content = self.my_re.sub(overlays, self.data)
+
+ if not self.my_re.search(content):
+ raise Exception('Ups, failed to set a proper PORTDIR_OVERLAY entry '
+ 'in file ' + self.path +'! Did not overwrite the fi'
+ 'le.')
+
+ try:
+ make_conf = codecs.open(self.path, 'w', 'utf-8')
+
+ make_conf.write(content)
+
+ make_conf.close()
+
+ except Exception, error:
+ raise Exception('Failed to read "' + self.path + '".\nError was:\n'
+ + str(error))
+
+ def content(self):
+ '''
+ Returns the content of the /etc/make.conf file.
+ '''
+ try:
+ make_conf = codecs.open(self.path, 'r', 'utf-8')
+
+ self.data = make_conf.read()
+
+ make_conf.close()
+
+ except Exception, error:
+ raise Exception('Failed to read "' + self.path + '".\nError was:\n'
+ + str(error))
diff --git a/overlord/overlays/__init__.py b/overlord/overlays/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/overlord/overlays/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/overlord/overlays/bzr.py b/overlord/overlays/bzr.py
new file mode 100644
index 0000000..0b06f94
--- /dev/null
+++ b/overlord/overlays/bzr.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord BZR OVERLAY HANDLER
+#################################################################################
+# File: bzr.py
+#
+# Handles bzr overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Adrian Perez, Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Adrian Perez <moebius@connectical.net>
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+'''Should work with any version of Bzr equal to or better than 0.7 --
+ caution: tested only with 0.8 and 0.8.2...'''
+
+__version__ = "$Id: bzr.py 236 2006-09-05 20:39:37Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class BzrOverlay
+#
+#-------------------------------------------------------------------------------
+
+class BzrOverlay(OverlaySource):
+ ''' Handles bzr overlays.'''
+
+ type = 'Bzr'
+ type_key = 'bzr'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(BzrOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ # bzr get SOURCE TARGET
+ args = ['get', self.src + '/', path([base, self.parent.name])]
+ return self.run_command(*args)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ # bzr pull --overwrite SOURCE
+ args = ['pull', '--overwrite', self.src]
+ return self.run_command(*args, cwd=path([base, self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'bzr',
+ 'dev-vcs/bzr'),])
diff --git a/overlord/overlays/cvs.py b/overlord/overlays/cvs.py
new file mode 100644
index 0000000..d40d38e
--- /dev/null
+++ b/overlord/overlays/cvs.py
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord CVS OVERLAY HANDLER
+#################################################################################
+# File: cvs.py
+#
+# Handles cvs overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+''' Cvs overlay support.'''
+
+__version__ = "$Id$"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import xml.etree.ElementTree as ET # Python 2.5
+
+from overlord.utils import path, ensure_unicode
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class CvsOverlay
+#
+#-------------------------------------------------------------------------------
+
+class CvsOverlay(OverlaySource):
+ ''' Handles cvs overlays.'''
+
+ type = 'cvs'
+ type_key = 'cvs'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(CvsOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ _subpath = xml.find('subpath')
+ if _subpath != None:
+ self.subpath = ensure_unicode(_subpath.text.strip())
+ elif 'subpath' in xml.attrib:
+ self.subpath = ensure_unicode(xml.attrib['subpath'])
+ else:
+ self.subpath = ''
+
+ def __eq__(self, other):
+ res = super(CvsOverlay, self).__eq__(other) \
+ and self.subpath == other.subpath
+ return res
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ # overrider
+ def to_xml_hook(self, repo_elem):
+ if self.subpath:
+ _subpath = ET.Element('subpath')
+ _subpath.text = self.subpath
+ repo_elem.append(_subpath)
+ del _subpath
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ # cvs [-q] co -d SOURCE SCOPE
+ args = []
+ if quiet:
+ args.append('-q')
+ args.append('co')
+ args.append('-d')
+ args.append(self.parent.name)
+ args.append(self.subpath)
+
+ return self.run_command(*args, cwd=base, env=dict(CVSROOT=self.src))
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ # cvs [-q] update -d
+ args = []
+ if quiet:
+ args.append('-q')
+ args.append('update')
+ args.append('-d')
+ return self.run_command(*args, cwd=path([base, self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'cvs',
+ 'dev-vcs/cvs'),])
diff --git a/overlord/overlays/darcs.py b/overlord/overlays/darcs.py
new file mode 100644
index 0000000..e253861
--- /dev/null
+++ b/overlord/overlays/darcs.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord DARCS OVERLAY HANDLER
+#################################################################################
+# File: darcs.py
+#
+# Handles darcs overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel, Andres Loeh
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Andres Loeh <kosmikus@gentoo.org>
+#
+''' Darcs overlay support.'''
+
+__version__ = "$Id: darcs.py 236 2006-09-05 20:39:37Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class BzrOverlay
+#
+#-------------------------------------------------------------------------------
+
+class DarcsOverlay(OverlaySource):
+ ''' Handles darcs overlays.'''
+
+ type = 'Darcs'
+ type_key = 'darcs'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(DarcsOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ # darcs get --partial SOURCE TARGET
+ args = ['get', '--partial', self.src + '/', path([base, self.parent.name])]
+ return self.run_command(*args)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ # darcs pull --all SOURCE
+ args = ['pull', '--all', self.src]
+ return self.run_command(*args, cwd=path([base, self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'darcs',
+ 'dev-vcs/darcs'),])
diff --git a/overlord/overlays/g_common.py b/overlord/overlays/g_common.py
new file mode 100644
index 0000000..20f2eae
--- /dev/null
+++ b/overlord/overlays/g_common.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord G-COMMON OVERLAY HANDLER
+#################################################################################
+# File: g_common.py
+#
+# Handles g-common-style repositories
+#
+# Copyright:
+# (c) 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Auke Booij <auke@tulcod.com>
+#
+''' G-common repository support.'''
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import os
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class GCommonOverlay
+#
+#-------------------------------------------------------------------------------
+
+class GCommonOverlay(OverlaySource):
+ ''' Handles g-common-style repositories.'''
+
+ type = 'g-common'
+ type_key = 'g-common'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+ super(GCommonOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+ #split source into driver and remote uri.
+ self.driver=self.src[:self.src.find(' ')]
+ self.remote_uri=self.src[self.src.find(' ')+1:]
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ os.makedirs(os.path.join(base,self.parent.name))
+ return self.sync(base, quiet)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ args = [os.path.join(base,self.parent.name), 'sync', self.driver, self.remote_uri]
+ returncode=self.run_command(*args,cwd=path([base,self.parent.name]))
+ if returncode: return returncode
+ args = [os.path.join(base,self.parent.name), 'generate-tree']
+ return self.run_command(*args,cwd=path([base,self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported(
+ [(self.command(),
+ 'g-common',
+ 'app-portage/g-common'),
+ ('/usr/share/g-common/drivers/'+self.driver+'.cfg',
+ 'g-common for '+self.driver,
+ 'app-portage/g-'+self.driver),])
diff --git a/overlord/overlays/git.py b/overlord/overlays/git.py
new file mode 100644
index 0000000..6aef61b
--- /dev/null
+++ b/overlord/overlays/git.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord GIT OVERLAY HANDLER
+#################################################################################
+# File: git.py
+#
+# Handles git overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel, Stefan Schweizer
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Stefan Schweizer <genstef@gentoo.org>
+''' Git overlay support.'''
+
+__version__ = "$Id: git.py 146 2006-05-27 09:52:36Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class GitOverlay
+#
+#-------------------------------------------------------------------------------
+
+class GitOverlay(OverlaySource):
+ ''' Handles git overlays.'''
+
+ type = 'Git'
+ type_key = 'git'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(GitOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ def fix_git_source(source):
+ # http:// should get trailing slash, other protocols shouldn't
+ if source.split(':')[0] == 'http':
+ return source + '/'
+ return source
+
+ # git clone [-q] SOURCE TARGET
+ args = ['clone']
+ if quiet:
+ args.append('-q')
+ args.append(fix_git_source(self.src))
+ args.append(path([base, self.parent.name]))
+ return self.run_command(*args)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ args = ['pull']
+ if quiet:
+ args.append('-q')
+ return self.run_command(*args, cwd=path([base, self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'git',
+ 'dev-vcs/git'),])
diff --git a/overlord/overlays/mercurial.py b/overlord/overlays/mercurial.py
new file mode 100644
index 0000000..f4a7c2c
--- /dev/null
+++ b/overlord/overlays/mercurial.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord MERCURIAL OVERLAY HANDLER
+#################################################################################
+# File: darcs.py
+#
+# Handles darcs overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel, Andres Loeh
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Andres Loeh <kosmikus@gentoo.org>
+#
+''' Mercurial overlay support.'''
+
+__version__ = "$Id: mercurial.py 236 2006-09-05 20:39:37Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class MercurialOverlay
+#
+#-------------------------------------------------------------------------------
+
+class MercurialOverlay(OverlaySource):
+ ''' Handles mercurial overlays.'''
+
+ type = 'Mercurial'
+ type_key = 'mercurial'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(MercurialOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ # hg clone SOURCE TARGET
+ args = ['clone', self.src + '/', path([base, self.parent.name])]
+ return self.run_command(*args)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ # hg pull -u SOURCE
+ args = ['pull', '-u', self.src]
+ return self.run_command(*args, cwd=path([base, self.parent.name]))
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'mercurial',
+ 'dev-vcs/mercurial'),])
diff --git a/overlord/overlays/overlay.py b/overlord/overlays/overlay.py
new file mode 100644
index 0000000..4a3cae7
--- /dev/null
+++ b/overlord/overlays/overlay.py
@@ -0,0 +1,419 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord OVERLAY BASE CLASS
+#################################################################################
+# File: overlay.py
+#
+# Base class for the different overlay types.
+#
+# Copyright:
+# (c) 2005 - 2009 Gunnar Wrobel
+# (c) 2009 Sebastian Pipping
+# (c) 2009 Christian Groschupp
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Sebastian Pipping <sebastian@pipping.org>
+# Christian Groschupp <christian@groschupp.org>
+#
+''' Basic overlay class.'''
+
+__version__ = "$Id: overlay.py 273 2006-12-30 15:54:50Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import sys, re, os, os.path
+import codecs
+import locale
+import xml.etree.ElementTree as ET # Python 2.5
+
+from overlord.utils import (pad, terminal_width, get_encoding, encoder,
+ ensure_unicode)
+#from overlord.debug import OUT
+
+from overlord.overlays.bzr import BzrOverlay
+from overlord.overlays.darcs import DarcsOverlay
+from overlord.overlays.git import GitOverlay
+from overlord.overlays.g_common import GCommonOverlay
+from overlord.overlays.mercurial import MercurialOverlay
+from overlord.overlays.cvs import CvsOverlay
+from overlord.overlays.svn import SvnOverlay
+from overlord.overlays.rsync import RsyncOverlay
+from overlord.overlays.tar import TarOverlay
+
+#===============================================================================
+#
+# Constants
+#
+#-------------------------------------------------------------------------------
+
+OVERLAY_TYPES = dict((e.type_key, e) for e in (
+ GitOverlay,
+ GCommonOverlay,
+ CvsOverlay,
+ SvnOverlay,
+ RsyncOverlay,
+ TarOverlay,
+ BzrOverlay,
+ MercurialOverlay,
+ DarcsOverlay
+))
+
+QUALITY_LEVELS = 'core|stable|testing|experimental|graveyard'.split('|')
+
+WHITESPACE_REGEX = re.compile('\s+')
+
+#===============================================================================
+#
+# Class Overlay
+#
+#-------------------------------------------------------------------------------
+
+class Overlay(object):
+ ''' Derive the real implementations from this.'''
+
+ def __init__(self, xml, config, ignore = 0, quiet = False):
+ '''
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> import xml.etree.ElementTree as ET # Python 2.5
+ >>> document = ET.parse(here + '/../tests/testfiles/global-overlays.xml')
+ >>> overlays = document.findall('overlay') + document.findall('repo')
+ >>> a = Overlay(overlays[0], dict())
+ >>> a.name
+ u'wrobel'
+ >>> a.is_official()
+ True
+ >>> list(a.source_uris())
+ [u'https://overlays.gentoo.org/svn/dev/wrobel']
+ >>> a.owner_email
+ u'nobody@gentoo.org'
+ >>> a.description
+ u'Test'
+ >>> a.priority
+ 10
+ >>> b = Overlay(overlays[1], dict())
+ >>> b.is_official()
+ False
+ '''
+
+ self.output = config['output']
+ self._encoding_ = get_encoding(self.output)
+
+ def strip_text(node):
+ res = node.text
+ if res is None:
+ return ''
+ return res.strip()
+
+ _name = xml.find('name')
+ if _name != None:
+ self.name = ensure_unicode(strip_text(_name))
+ elif 'name' in xml.attrib:
+ self.name = ensure_unicode(xml.attrib['name'])
+ else:
+ raise Exception('Overlay is missing a "name" entry!')
+
+ _sources = xml.findall('source')
+ if _sources:
+ _sources = [e for e in _sources if 'type' in e.attrib]
+ elif ('src' in xml.attrib) and ('type' in xml.attrib):
+ s = ET.Element('source', type=xml.attrib['type'])
+ s.text = xml.attrib['src']
+ _sources = [s]
+ del s
+
+ if not _sources:
+ raise Exception('Overlay "' + self.name + '" is missing a "source" entry!')
+
+
+ def create_overlay_source(source_elem):
+ _type = source_elem.attrib['type']
+ try:
+ _class = OVERLAY_TYPES[_type]
+ except KeyError:
+ raise Exception('Unknown overlay type "%s"!' % _type)
+ _location = ensure_unicode(strip_text(source_elem))
+ return _class(self, xml, config, _location, ignore, quiet)
+
+ self.sources = [create_overlay_source(e) for e in _sources]
+
+
+ _owner = xml.find('owner')
+ if _owner == None:
+ _email = None
+ else:
+ _email = _owner.find('email')
+ if _owner != None and _email != None:
+ self.owner_email = ensure_unicode(strip_text(_email))
+ _name = _owner.find('name')
+ if _name != None:
+ self.owner_name = ensure_unicode(strip_text(_name))
+ else:
+ self.owner_name = None
+ elif 'contact' in xml.attrib:
+ self.owner_email = ensure_unicode(xml.attrib['contact'])
+ self.owner_name = None
+ else:
+ self.owner_email = ''
+ self.owner_name = None
+ if not ignore:
+ raise Exception('Overlay "' + self.name + '" is missing a '
+ '"owner.email" entry!')
+ elif ignore == 1:
+ self.output.warn('Overlay "' + self.name + '" is missing a '
+ '"owner.email" entry!', 4)
+
+
+ _desc = xml.find('description')
+ if _desc != None:
+ d = WHITESPACE_REGEX.sub(' ', strip_text(_desc))
+ self.description = ensure_unicode(d)
+ del d
+ else:
+ self.description = ''
+ if not ignore:
+ raise Exception('Overlay "' + self.name + '" is missing a '
+ '"description" entry!')
+ elif ignore == 1:
+ self.output.warn('Overlay "' + self.name + '" is missing a '
+ '"description" entry!', 4)
+
+ if 'status' in xml.attrib:
+ self.status = ensure_unicode(xml.attrib['status'])
+ else:
+ self.status = None
+
+ self.quality = u'experimental'
+ if 'quality' in xml.attrib:
+ if xml.attrib['quality'] in set(QUALITY_LEVELS):
+ self.quality = ensure_unicode(xml.attrib['quality'])
+
+ if 'priority' in xml.attrib:
+ self.priority = int(xml.attrib['priority'])
+ else:
+ self.priority = 50
+
+ h = xml.find('homepage')
+ l = xml.find('link')
+ if h != None:
+ self.homepage = ensure_unicode(strip_text(h))
+ elif l != None:
+ self.homepage = ensure_unicode(strip_text(l))
+ else:
+ self.homepage = None
+
+ self.feeds = [ensure_unicode(strip_text(e)) for e in xml.findall('feed')]
+
+
+ def __eq__(self, other):
+ for i in ('description', 'homepage', 'name', 'owner_email',
+ 'owner_name', 'priority', 'status'):
+ if getattr(self, i) != getattr(other, i):
+ return False
+ for i in self.sources + other.sources:
+ if not i in self.sources:
+ return False
+ if not i in other.sources:
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def set_priority(self, priority):
+ '''Set the priority of this overlay.'''
+
+ self.priority = int(priority)
+
+ def to_xml(self):
+ '''Convert to xml.'''
+
+ repo = ET.Element('repo')
+ if self.status != None:
+ repo.attrib['status'] = self.status
+ repo.attrib['quality'] = self.quality
+ repo.attrib['priority'] = str(self.priority)
+ name = ET.Element('name')
+ name.text = self.name
+ repo.append(name)
+ desc = ET.Element('description')
+ desc.text = self.description
+ repo.append(desc)
+ if self.homepage != None:
+ homepage = ET.Element('homepage')
+ homepage.text = self.homepage
+ repo.append(homepage)
+ owner = ET.Element('owner')
+ repo.append(owner)
+ owner_email = ET.Element('email')
+ owner_email.text = self.owner_email
+ owner.append(owner_email)
+ if self.owner_name != None:
+ owner_name = ET.Element('name')
+ owner_name.text = self.owner_name
+ owner.append(owner_name)
+ for i in self.sources:
+ source = ET.Element('source', type=i.__class__.type_key)
+ source.text = i.src
+ repo.append(source)
+ del source
+ for i in self.sources:
+ # NOTE: Two loops on purpose so the
+ # hooks are called with all sources in
+ i.to_xml_hook(repo)
+ for i in self.feeds:
+ feed = ET.Element('feed')
+ feed.text = i
+ repo.append(feed)
+ del feed
+ return repo
+
+ def add(self, base, quiet = False):
+ res = 1
+ for s in self.sources:
+ try:
+ res = s.add(base, quiet)
+ if res == 0:
+ # Worked, throw other sources away
+ self.sources = [s]
+ break
+ except Exception, error:
+ self.output.warn(str(error), 4)
+ return res
+
+ def sync(self, base, quiet = False):
+ assert len(self.sources) == 1
+ return self.sources[0].sync(base, quiet)
+
+ def delete(self, base):
+ assert len(self.sources) == 1
+ return self.sources[0].delete(base)
+
+ def __str__(self):
+ '''
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> import xml.etree.ElementTree as ET # Python 2.5
+ >>> document = ET.parse(here + '/../tests/testfiles/global-overlays.xml')
+ >>> overlays = document.findall('overlay') + document.findall('repo')
+ >>> a = Overlay(overlays[0], dict())
+ >>> print str(a)
+ wrobel
+ ~~~~~~
+ Source : https://overlays.gentoo.org/svn/dev/wrobel
+ Contact : nobody@gentoo.org
+ Type : Subversion; Priority: 10
+ Quality : experimental
+ <BLANKLINE>
+ Description:
+ Test
+ <BLANKLINE>
+ '''
+
+ result = u''
+
+ result += self.name + u'\n' + (len(self.name) * u'~')
+
+ if len(self.sources) == 1:
+ result += u'\nSource : ' + self.sources[0].src
+ else:
+ result += u'\nSources:'
+ for i, v in enumerate(self.sources):
+ result += '\n %d. %s' % (i + 1, v.src)
+ result += '\n'
+
+ if self.owner_name != None:
+ result += u'\nContact : %s <%s>' % (self.owner_name, self.owner_email)
+ else:
+ result += u'\nContact : ' + self.owner_email
+ if len(self.sources) == 1:
+ result += u'\nType : ' + self.sources[0].type
+ else:
+ result += u'\nType : ' + '/'.join(sorted(set(e.type for e in self.sources)))
+ result += u'; Priority: ' + str(self.priority) + u'\n'
+ result += u'Quality : ' + self.quality + u'\n'
+
+
+ description = self.description
+ description = re.compile(u' +').sub(u' ', description)
+ description = re.compile(u'\n ').sub(u'\n', description)
+ result += u'\nDescription:'
+ result += u'\n '.join((u'\n' + description).split(u'\n'))
+ result += u'\n'
+
+ if self.homepage != None:
+ link = self.homepage
+ link = re.compile(u' +').sub(u' ', link)
+ link = re.compile(u'\n ').sub(u'\n', link)
+ result += u'\nLink:'
+ result += u'\n '.join((u'\n' + link).split(u'\n'))
+ result += u'\n'
+
+ if self.feeds:
+ result += u'\n%s:' % ((len(self.feeds) == 1) and "Feed" or "Feeds")
+ for i in self.feeds:
+ result += u'\n %s' % i
+ result += u'\n'
+
+ return encoder(result, self._encoding_)
+
+ def short_list(self, width = 0):
+ '''
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> import xml.etree.ElementTree as ET # Python 2.5
+ >>> document = ET.parse(here + '/../tests/testfiles/global-overlays.xml')
+ >>> overlays = document.findall('repo') + document.findall('overlay')
+ >>> a = Overlay(overlays[0], dict())
+ >>> print a.short_list(80)
+ wrobel [Subversion] (https://o.g.o/svn/dev/wrobel )
+ '''
+
+ name = pad(self.name, 25)
+
+ if len(set(e.type for e in self.sources)) == 1:
+ _type = self.sources[0].type
+ else:
+ _type = '%s/..' % self.sources[0].type
+
+ mtype = ' [' + pad(_type, 10) + ']'
+ if not width:
+ width = terminal_width()
+ srclen = width - 43
+ source = ', '.join(self.source_uris())
+ if len(source) > srclen:
+ source = source.replace("overlays.gentoo.org", "o.g.o")
+ source = ' (' + pad(source, srclen) + ')'
+
+ return encoder(name + mtype + source, self._encoding_)
+
+ def is_official(self):
+ '''Is the overlay official?'''
+
+ return self.status == 'official'
+
+ def is_supported(self):
+ return any(e.is_supported() for e in self.sources)
+
+ def source_uris(self):
+ for i in self.sources:
+ yield i.src
+
+ def source_types(self):
+ for i in self.sources:
+ yield i.type
+
+
+#================================================================================
+#
+# Testing
+#
+#--------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(sys.modules[__name__])
diff --git a/overlord/overlays/rsync.py b/overlord/overlays/rsync.py
new file mode 100644
index 0000000..d5dbd48
--- /dev/null
+++ b/overlord/overlays/rsync.py
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord RSYNC OVERLAY HANDLER
+#################################################################################
+# File: rsync.py
+#
+# Handles rsync overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+''' Rsync overlay support.'''
+
+__version__ = "$Id: rsync.py 236 2006-09-05 20:39:37Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class RsyncOverlay
+#
+#-------------------------------------------------------------------------------
+
+class RsyncOverlay(OverlaySource):
+ ''' Handles rsync overlays.'''
+
+ type = 'Rsync'
+ type_key = 'rsync'
+
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(RsyncOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ super(RsyncOverlay, self).add(base)
+
+ return self.sync(base)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ # rsync OPTIONS [-q] SOURCE TARGET
+ args = ['-rlptDvz', '--progress', '--delete', '--delete-after', '--timeout=180',
+ '--exclude=distfiles/*', '--exclude=local/*', '--exclude=packages/*']
+ if quiet:
+ args.append('-q')
+ args.append(self.src + '/')
+ args.append(path([base, self.parent.name]))
+
+ return self.run_command(*args)
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'rsync',
+ 'net-misc/rsync'),])
diff --git a/overlord/overlays/source.py b/overlord/overlays/source.py
new file mode 100644
index 0000000..c645fa8
--- /dev/null
+++ b/overlord/overlays/source.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord OVERLAY SOURCE BASE CLASS
+#################################################################################
+# File: source.py
+#
+# Base class for the different overlay types.
+#
+# Copyright:
+# (c) 2010 Sebastian Pipping
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Sebastian Pipping <sebastian@pipping.org>
+
+import os
+import copy
+import sys
+import shutil
+import subprocess
+#from overlord.debug import OUT
+from overlord.utils import path
+
+
+def _resolve_command(command):
+ if os.path.isabs(command):
+ if not os.path.exists(command):
+ raise Exception('Program "%s" not found' % command)
+ return ('File', command)
+ else:
+ kind = 'Command'
+ env_path = os.environ['PATH']
+ for d in env_path.split(os.pathsep):
+ f = os.path.join(d, command)
+ if os.path.exists(f):
+ return ('Command', f)
+ raise Exception('Cound not resolve command "%s" based on PATH "%s"' % (command, env_path))
+
+
+def require_supported(binaries):
+ for command, mtype, package in binaries:
+ found = False
+ kind, path = _resolve_command(command)
+ if not path:
+ raise Exception(kind + ' ' + command + ' seems to be missing!'
+ ' Overlay type "' + mtype + '" not support'
+ 'ed. Did you emerge ' + package + '?')
+ return True
+
+
+class OverlaySource(object):
+
+ type_key = None
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+ self.parent = parent
+ self.src = _location
+ self.config = config
+ self.ignore = ignore
+ self.quiet = quiet
+ self.output = config['output']
+
+ def __eq__(self, other):
+ return self.src == other.src
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def add(self, base, quiet = False):
+ '''Add the overlay.'''
+
+ mdir = path([base, self.parent.name])
+
+ if os.path.exists(mdir):
+ raise Exception('Directory ' + mdir + ' already exists. Will not ov'
+ 'erwrite its contents!')
+
+ os.makedirs(mdir)
+
+ def sync(self, base, quiet = False):
+ '''Sync the overlay.'''
+ pass
+
+ def delete(self, base):
+ '''Delete the overlay.'''
+ mdir = path([base, self.parent.name])
+
+ if not os.path.exists(mdir):
+ self.output.warn('Directory ' + mdir + ' did not exist, no files deleted.')
+ return
+
+ self.output.info('Deleting directory "%s"' % mdir, 2)
+ shutil.rmtree(mdir)
+
+ def supported(self):
+ '''Is the overlay type supported?'''
+ return True
+
+ def is_supported(self):
+ '''Is the overlay type supported?'''
+
+ try:
+ self.supported()
+ return True
+ except:
+ return False
+
+ def command(self):
+ return self.config['%s_command' % self.__class__.type_key]
+
+ def run_command(self, *args, **kwargs):
+ file_to_run = _resolve_command(self.command())[1]
+ args = (file_to_run, ) + args
+ assert('pwd' not in kwargs) # Bug detector
+
+ cwd = kwargs.get('cwd', None)
+ env = None
+ env_updates = None
+ if 'env' in kwargs:
+ # Build actual env from surrounding plus updates
+ env_updates = kwargs['env']
+ env = copy.copy(os.environ)
+ env.update(env_updates)
+
+ command_repr = ' '.join(args)
+ if env_updates:
+ command_repr = '%s %s' % (' '.join('%s=%s' % (k, v) for (k, v) in sorted(env_updates.items())), command_repr)
+ if cwd:
+ command_repr = '( cd %s && %s )' % (cwd, command_repr)
+
+ self.output.info('Running... # %s' % command_repr, 2)
+
+ if self.quiet:
+ input_source = subprocess.PIPE
+ output_target = open('/dev/null', 'w')
+ else:
+ # Re-use parent file descriptors
+ input_source = self.config['stdin']
+ output_target = self.config['stdout']
+
+ proc = subprocess.Popen(args,
+ stdin=input_source,
+ stdout=output_target,
+ stderr=self.config['stderr'],
+ cwd=cwd,
+ env=env)
+
+ if self.quiet:
+ # Make child non-interactive
+ proc.stdin.close()
+
+ result = proc.wait()
+
+ if self.quiet:
+ output_target.close()
+
+ return result
+
+
+ def to_xml_hook(self, repo_elem):
+ pass
diff --git a/overlord/overlays/svn.py b/overlord/overlays/svn.py
new file mode 100644
index 0000000..1570450
--- /dev/null
+++ b/overlord/overlays/svn.py
@@ -0,0 +1,86 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord SVN OVERLAY HANDLER
+#################################################################################
+# File: svn.py
+#
+# Handles subversion overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+''' Subversion overlay support.'''
+
+__version__ = "$Id: svn.py 236 2006-09-05 20:39:37Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+from overlord.utils import path
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class SvnOverlay
+#
+#-------------------------------------------------------------------------------
+
+class SvnOverlay(OverlaySource):
+ ''' Handles subversion overlays.'''
+
+ type = 'Subversion'
+ type_key = 'svn'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(SvnOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ super(SvnOverlay, self).add(base)
+
+ args = ['co']
+ if quiet:
+ args.append('-q')
+ args.append(self.src + '/@')
+ args.append(path([base, self.parent.name]))
+
+ return self.run_command(*args)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+
+ self.supported()
+
+ def checkout_location():
+ # Append '@' iff needed
+ # Keeps users of SVN <1.6.5 happy in more cases (bug #313303)
+ repo_part = self.parent.name
+ if self.parent.name.find('@') != -1:
+ repo_part = repo_part + '@'
+ return path([base, repo_part])
+
+ # svn up [-q] TARGET
+ args = ['up']
+ if quiet:
+ args.append('-q')
+ args.append(checkout_location())
+
+ return self.run_command(*args)
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'svn',
+ 'dev-vcs/subversion'),])
diff --git a/overlord/overlays/tar.py b/overlord/overlays/tar.py
new file mode 100644
index 0000000..425fe26
--- /dev/null
+++ b/overlord/overlays/tar.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord TAR OVERLAY HANDLER
+#################################################################################
+# File: tar.py
+#
+# Handles tar overlays
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+''' Tar overlay support.'''
+
+__version__ = "$Id: tar.py 310 2007-04-09 16:30:40Z wrobel $"
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import os, os.path, sys, urllib2, shutil, tempfile
+import xml.etree.ElementTree as ET # Python 2.5
+
+from overlord.utils import path, ensure_unicode
+#from overlord.debug import OUT
+from overlord.overlays.source import OverlaySource, require_supported
+
+#===============================================================================
+#
+# Class TarOverlay
+#
+#-------------------------------------------------------------------------------
+
+class TarOverlay(OverlaySource):
+ ''' Handles tar overlays.
+
+ >>> from overlord.debug import OUT
+ >>> import xml.etree.ElementTree as ET # Python 2.5
+ >>> repo = ET.Element('repo')
+ >>> repo_name = ET.Element('name')
+ >>> repo_name.text = 'dummy'
+ >>> desc = ET.Element('description')
+ >>> desc.text = 'Dummy description'
+ >>> owner = ET.Element('owner')
+ >>> owner_email = ET.Element('email')
+ >>> owner_email.text = 'dummy@example.org'
+ >>> owner[:] = [owner_email]
+ >>> source = ET.Element('source', type='tar')
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> source.text = 'file://' + here + '/../tests/testfiles/overlord-test.tar.bz2'
+ >>> subpath = ET.Element('subpath')
+ >>> subpath.text = 'overlord-test'
+ >>> repo[:] = [repo_name, desc, owner, source, subpath]
+ >>> config = {'tar_command':'/bin/tar'}
+ >>> testdir = os.tmpnam()
+ >>> os.mkdir(testdir)
+ >>> from overlord.overlays.overlay import Overlay
+ >>> a = Overlay(repo, config, quiet=False)
+ >>> OUT.color_off()
+ >>> a.add(testdir) #doctest: +ELLIPSIS
+ * Running... # /bin/tar -v -x -f...
+ >>> sorted(os.listdir(testdir + '/dummy'))
+ ['app-admin', 'app-portage']
+ >>> shutil.rmtree(testdir)
+ '''
+
+ type = 'Tar'
+ type_key = 'tar'
+
+ def __init__(self, parent, xml, config, _location, ignore = 0, quiet = False):
+
+ super(TarOverlay, self).__init__(parent, xml, config, _location, ignore, quiet)
+
+ _subpath = xml.find('subpath')
+ if _subpath != None:
+ self.subpath = ensure_unicode(_subpath.text.strip())
+ elif 'subpath' in xml.attrib:
+ self.subpath = ensure_unicode(xml.attrib['subpath'])
+ else:
+ self.subpath = ''
+
+ self.output = config['output']
+
+ def __eq__(self, other):
+ res = super(TarOverlay, self).__eq__(other) \
+ and self.subpath == other.subpath
+ return res
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ # overrider
+ def to_xml_hook(self, repo_elem):
+ if self.subpath:
+ _subpath = ET.Element('subpath')
+ _subpath.text = self.subpath
+ repo_elem.append(_subpath)
+ del _subpath
+
+ def _extract(self, base, tar_url, dest_dir):
+ ext = '.tar.noidea'
+ for i in [('tar.%s' % e) for e in ('bz2', 'gz', 'lzma', 'xz', 'Z')] \
+ + ['tgz', 'tbz', 'taz', 'tlz', 'txz']:
+ candidate_ext = '.%s' % i
+ if self.src.endswith(candidate_ext):
+ ext = candidate_ext
+ break
+
+ try:
+ tar = urllib2.urlopen(tar_url).read()
+ except Exception, error:
+ raise Exception('Failed to fetch the tar package from: '
+ + self.src + '\nError was:' + str(error))
+
+ pkg = path([base, self.parent.name + ext])
+
+ try:
+ out_file = open(pkg, 'w')
+ out_file.write(tar)
+ out_file.close()
+ except Exception, error:
+ raise Exception('Failed to store tar package in '
+ + pkg + '\nError was:' + str(error))
+
+ # tar -v -x -f SOURCE -C TARGET
+ args = ['-v', '-x', '-f', pkg, '-C', dest_dir]
+ result = self.run_command(*args)
+
+ os.unlink(pkg)
+ return result
+
+ def _add_unchecked(self, base, quiet):
+ def try_to_wipe(folder):
+ if not os.path.exists(folder):
+ return
+
+ try:
+ self.output.info('Deleting directory "%s"' % folder, 2)
+ shutil.rmtree(folder)
+ except Exception, error:
+ raise Exception('Failed to remove unnecessary tar structure "'
+ + folder + '"\nError was:' + str(error))
+
+ final_path = path([base, self.parent.name])
+ temp_path = tempfile.mkdtemp(dir=base)
+ try:
+ result = self._extract(base=base, tar_url=self.src, dest_dir=temp_path)
+ except Exception, error:
+ try_to_wipe(temp_path)
+ raise error
+
+ if result == 0:
+ if self.subpath:
+ source = temp_path + '/' + self.subpath
+ else:
+ source = temp_path
+
+ if os.path.exists(source):
+ if os.path.exists(final_path):
+ self.delete(base)
+
+ try:
+ os.rename(source, final_path)
+ except Exception, error:
+ raise Exception('Failed to rename tar subdirectory ' +
+ source + ' to ' + final_path + '\nError was:'
+ + str(error))
+ os.chmod(final_path, 0755)
+ else:
+ raise Exception('Given subpath "' + source + '" does not exist '
+ ' in the tar package!')
+
+ try_to_wipe(temp_path)
+ return result
+
+ def add(self, base, quiet = False):
+ '''Add overlay.'''
+
+ self.supported()
+
+ final_path = path([base, self.parent.name])
+
+ if os.path.exists(final_path):
+ raise Exception('Directory ' + final_path + ' already exists. Will not ov'
+ 'erwrite its contents!')
+
+ return self._add_unchecked(base, quiet)
+
+ def sync(self, base, quiet = False):
+ '''Sync overlay.'''
+ self.supported()
+ self._add_unchecked(base, quiet)
+
+ def supported(self):
+ '''Overlay type supported?'''
+
+ return require_supported([(self.command(), 'tar', 'app-arch/tar'), ])
+
+if __name__ == '__main__':
+ import doctest
+
+ # Ignore warnings here. We are just testing
+ from warnings import filterwarnings, resetwarnings
+ filterwarnings('ignore')
+
+ doctest.testmod(sys.modules[__name__])
+
+ resetwarnings()
diff --git a/overlord/tests/dtest.py b/overlord/tests/dtest.py
new file mode 100644
index 0000000..d380b7f
--- /dev/null
+++ b/overlord/tests/dtest.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord DOCTEST AGGREGATOR
+#################################################################################
+# File: dtest.py
+#
+# Combines the doctests that are available for the different modules
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+'''Aggregates doctests from all modules that provide such tests.'''
+
+__version__ = '$Id: dtest.py 237 2006-09-05 21:18:54Z wrobel $'
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import unittest, doctest
+
+# On module creation:
+
+# 1.) Check header section (copyright notice)
+# 2.) Add module doc string
+# 3.) Add version string
+# 4.) Add testing handler at bottom of module
+# 5.) Add module into tests/dtest.py. Check that tests run through
+# 6.) Run pylint over the code. Fix any reasonable complaints.
+# 7.) Whitespace clean the buffer.
+# 8.) Add svn:keywords "Id" to file.
+
+# On module change:
+
+# 1.) Check header section (copyright notice)
+# 5.) Check that tests run through
+# 6.) Run pylint over the code. Fix any reasonable complaints.
+# 7.) Whitespace clean the buffer.
+
+# clean modules : CT
+# not yet clean : UT
+# clean but no testing : CN
+# unclean but no testing: UN
+
+import overlord.action #CT
+import overlord.config #CT
+import overlord.db #CT
+import overlord.dbbase #CT
+import overlord.utils #CT
+import overlord.overlays.overlay #CT
+import overlord.overlays.tar #CT
+
+#===============================================================================
+#
+# Test Suite
+#
+#-------------------------------------------------------------------------------
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocTestSuite(overlord.action),
+ doctest.DocTestSuite(overlord.config),
+ doctest.DocTestSuite(overlord.db),
+ doctest.DocTestSuite(overlord.dbbase),
+ doctest.DocTestSuite(overlord.utils),
+ doctest.DocTestSuite(overlord.overlays.overlay),
+ doctest.DocTestSuite(overlord.overlays.tar),
+ ))
+
+#===============================================================================
+#
+# Run Testing
+#
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ # Ignore warnings here. We are just testing
+ from warnings import filterwarnings, resetwarnings
+ filterwarnings('ignore')
+
+ unittest.main(defaultTest='test_suite')
+
+ resetwarnings()
diff --git a/overlord/tests/external.py b/overlord/tests/external.py
new file mode 100644
index 0000000..aa43a3f
--- /dev/null
+++ b/overlord/tests/external.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+#################################################################################
+# EXTENRAL overlord TESTS
+#################################################################################
+# File: external.py
+#
+# Runs external (non-doctest) test cases.
+#
+# Copyright:
+# (c) 2009 Sebastian Pipping
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Sebastian Pipping <sebastian@pipping.org>
+#
+'''Runs external (non-doctest) test cases.'''
+
+import unittest
+import os
+import tempfile
+import shutil
+import urllib
+from overlord.dbbase import DbBase
+from warnings import filterwarnings, resetwarnings
+
+HERE = os.path.dirname(os.path.realpath(__file__))
+
+
+class Unicode(unittest.TestCase):
+ def _overlays_bug(self, number):
+ config = {}
+ filename = os.path.join(HERE, 'testfiles', 'overlays_bug_%d.xml' % number)
+ o = DbBase([filename], config)
+ for verbose in (True, False):
+ for t in o.list(verbose=verbose):
+ print t[0]
+ print
+
+ def test_184449(self):
+ self._overlays_bug(184449)
+
+ def test_286290(self):
+ self._overlays_bug(286290)
+
+
+class FormatSubpathCategory(unittest.TestCase):
+ def _run(self, number):
+ config = {}
+ filename1 = os.path.join(HERE, 'testfiles',
+ 'subpath-%d.xml' % number)
+
+ # Read, write, re-read, compare
+ os1 = DbBase([filename1], config)
+ filename2 = os.tmpnam()
+ os1.write(filename2)
+ os2 = DbBase([filename2], config)
+ os.unlink(filename2)
+ self.assertTrue(os1 == os2)
+
+ # Pass original overlays
+ return os1
+
+ def test(self):
+ os1 = self._run(1)
+ os2 = self._run(2)
+
+ # Same content from old/overlord-global.txt
+ # and new/repositories.xml format?
+ self.assertTrue(os1 == os2)
+
+
+# http://bugs.gentoo.org/show_bug.cgi?id=304547
+class TarAddRemoveSync(unittest.TestCase):
+ def test(self):
+ repo_name = 'tar-test-overlay'
+ tar_source_path = os.path.join(HERE, 'testfiles', 'overlord-test.tar.bz2')
+
+ # Duplicate test tarball (so we have it deletable for later)
+ (_, temp_tarball_path) = tempfile.mkstemp()
+ shutil.copyfile(tar_source_path, temp_tarball_path)
+
+ # Write overlay collection XML
+ xml_text = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<repositories xmlns="" version="1.0">
+ <repo quality="experimental" status="unofficial">
+ <name>%(repo_name)s</name>
+ <description>XXXXXXXXXXX</description>
+ <owner>
+ <email>foo@exmaple.org</email>
+ </owner>
+ <source type="tar">file://%(temp_tarball_url)s</source>
+ </repo>
+</repositories>
+""" % { 'temp_tarball_url':urllib.pathname2url(temp_tarball_path),
+ 'repo_name':repo_name}
+ (fd, temp_collection_path) = tempfile.mkstemp()
+ f = os.fdopen(fd, 'w')
+ f.write(xml_text)
+ f.close()
+
+ # Make playground directory
+ temp_dir_path = tempfile.mkdtemp()
+
+ # Make DB from it
+ config = {'tar_command':'/bin/tar'}
+ db = DbBase([temp_collection_path], config)
+
+ specific_overlay_path = os.path.join(temp_dir_path, repo_name)
+ o = db.select('tar-test-overlay')
+
+ # Actual testcase
+ o.add(temp_dir_path)
+ self.assertTrue(os.path.exists(specific_overlay_path))
+ # (1/2) Sync with source available
+ o.sync(temp_dir_path)
+ self.assertTrue(os.path.exists(specific_overlay_path))
+ os.unlink(temp_tarball_path)
+ try:
+ # (2/2) Sync with source _not_ available
+ o.sync(temp_dir_path)
+ except:
+ pass
+ self.assertTrue(os.path.exists(specific_overlay_path))
+ o.delete(temp_dir_path)
+ self.assertFalse(os.path.exists(specific_overlay_path))
+
+ # Cleanup
+ os.unlink(temp_collection_path)
+ os.rmdir(temp_dir_path)
+
+
+if __name__ == '__main__':
+ filterwarnings('ignore')
+ unittest.main()
+ resetwarnings()
diff --git a/overlord/tests/pylintrc b/overlord/tests/pylintrc
new file mode 100644
index 0000000..b05498a
--- /dev/null
+++ b/overlord/tests/pylintrc
@@ -0,0 +1,19 @@
+[MESSAGES CONTROL]
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=IRC
+
+# Disable the message(s) with the given id(s).
+# :W0613: *Unused argument %r*
+# :W0702: *No exception type(s) specified*
+# :W0703: *Catch "Exception"*
+disable-msg=W0613,W0702,W0703
+
+
+[REPORTS]
+
+# Include message's id in output
+include-ids=yes
+
+# Tells whether to display a full report or only the messages
+reports=no
diff --git a/overlord/tests/testfiles/global-overlays.xml b/overlord/tests/testfiles/global-overlays.xml
new file mode 100644
index 0000000..d770692
--- /dev/null
+++ b/overlord/tests/testfiles/global-overlays.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" ?>
+<layman>
+
+ <overlay
+ type = "svn"
+ src = "https://overlays.gentoo.org/svn/dev/wrobel"
+ contact = "nobody@gentoo.org"
+ name = "wrobel"
+ status = "official"
+ priority = "10">
+
+ <description>
+ Test
+ </description>
+
+ </overlay>
+
+ <overlay
+ type = "rsync"
+ src = "rsync://gunnarwrobel.de/wrobel-stable"
+ contact = "nobody@gentoo.org"
+ name = "wrobel-stable">
+
+ <description>
+ A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org].
+ </description>
+
+ </overlay>
+
+</layman>
diff --git a/overlord/tests/testfiles/layman-test.tar.bz2 b/overlord/tests/testfiles/layman-test.tar.bz2
new file mode 100644
index 0000000..85ee7fd
--- /dev/null
+++ b/overlord/tests/testfiles/layman-test.tar.bz2
Binary files differ
diff --git a/overlord/tests/testfiles/make.conf b/overlord/tests/testfiles/make.conf
new file mode 100644
index 0000000..9a8aac6
--- /dev/null
+++ b/overlord/tests/testfiles/make.conf
@@ -0,0 +1,345 @@
+# Copyright 1999-2004 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-src/portage/cnf/make.conf.x86,v 1.5.2.5 2005/04/13 15:28:38 jstubbs Exp $
+# Contains local system settings for Portage system
+
+# Please review 'man make.conf' for more information.
+
+# Build-time functionality
+# ========================
+#
+# The USE variable is used to enable optional build-time functionality. For
+# example, quite a few packages have optional X, gtk or GNOME functionality
+# that can only be enabled or disabled at compile-time. Gentoo Linux has a
+# very extensive set of USE variables described in our USE variable HOWTO at
+# http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=1
+#
+# The available list of use flags with descriptions is in your portage tree.
+# Use 'less' to view them: --> less /usr/portage/profiles/use.desc <--
+#
+# 'ufed' is an ncurses/dialog interface available in portage to make handling
+# useflags for you. 'emerge app-portage/ufed'
+#
+# Example:
+
+# Use flags will be handled by polymeraZe
+USE="-*"
+
+# Host Setting
+# ============
+#
+# DO NOT CHANGE THIS SETTING UNLESS YOU ARE USING STAGE1!
+# Change this line as appropriate (i686, i586, i486 or i386).
+# All modern systems (even Athlons) should use "i686-pc-linux-gnu".
+# All K6's are i586.
+CHOST="i686-pc-linux-gnu"
+
+# Host and optimization settings
+# ==============================
+#
+# For optimal performance, enable a CFLAGS setting appropriate for your CPU.
+#
+# Please note that if you experience strange issues with a package, it may be
+# due to gcc's optimizations interacting in a strange way. Please test the
+# package (and in some cases the libraries it uses) at default optimizations
+# before reporting errors to developers.
+#
+# -mcpu=<cpu-type> means optimize code for the particular type of CPU without
+# breaking compatibility with other CPUs.
+#
+# -march=<cpu-type> means to take full advantage of the ABI and instructions
+# for the particular CPU; this will break compatibility with older CPUs (for
+# example, -march=athlon-xp code will not run on a regular Athlon, and
+# -march=i686 code will not run on a Pentium Classic.
+#
+# CPU types supported in gcc-3.2 and higher: athlon-xp, athlon-mp,
+# athlon-tbird, athlon, k6, k6-2, k6-3, i386, i486, i586 (Pentium), i686
+# (PentiumPro), pentium, pentium-mmx, pentiumpro, pentium2 (Celeron),
+# pentium3, and pentium4.
+#
+# Note that Gentoo Linux 1.4 and higher include at least gcc-3.2.
+#
+# CPU types supported in gcc-2.95*: k6, i386, i486, i586 (Pentium), i686
+# (Pentium Pro), pentium, pentiumpro Gentoo Linux 1.2 and below use gcc-2.95*
+#
+# CRITICAL WARNINGS: ****************************************************** #
+# K6 markings are deceptive. Avoid setting -march for them. See Bug #24379. #
+# Pentium-M CPU's should not enable sse2 until at least gcc-3.4. Bug 50616. #
+# ************************************************************************* #
+#
+# Decent examples:
+#
+#CFLAGS="-mcpu=athlon-xp -O3 -pipe"
+
+CFLAGS="-march=athlon-xp -O3 -pipe"
+
+
+# If you set a CFLAGS above, then this line will set your default C++ flags to
+# the same settings.
+CXXFLAGS="${CFLAGS}"
+
+# Advanced Masking
+# ================
+#
+# Gentoo is using a new masking system to allow for easier stability testing
+# on packages. KEYWORDS are used in ebuilds to mask and unmask packages based
+# on the platform they are set for. A special form has been added that
+# indicates packages and revisions that are expected to work, but have not yet
+# been approved for the stable set. '~arch' is a superset of 'arch' which
+# includes the unstable, in testing, packages. Users of the 'x86' architecture
+# would add '~x86' to ACCEPT_KEYWORDS to enable unstable/testing packages.
+# '~ppc', '~sparc' are the unstable KEYWORDS for their respective platforms.
+#
+# Please note that this is not for development, alpha, beta, nor cvs release
+# packages. "Broken" packages will not be added to testing and should not be
+# requested to be added. Alternative routes are available to developers
+# for experimental packages, and it is at their discretion to use them.
+#
+# DO NOT PUT ANYTHING BUT YOUR SPECIFIC ~ARCHITECTURE IN THE LIST.
+# IF YOU ARE UNSURE OF YOUR ARCH, OR THE IMPLICATIONS, DO NOT MODIFY THIS.
+#
+
+ACCEPT_KEYWORDS="x86"
+
+
+# Portage Directories
+# ===================
+#
+# Each of these settings controls an aspect of portage's storage and file
+# system usage. If you change any of these, be sure it is available when
+# you try to use portage. *** DO NOT INCLUDE A TRAILING "/" ***
+#
+# PORTAGE_TMPDIR is the location portage will use for compilations and
+# temporary storage of data. This can get VERY large depending upon
+# the application being installed.
+PORTAGE_TMPDIR=/var/tmp
+#
+# PORTDIR is the location of the portage tree. This is the repository
+# for all profile information as well as all ebuilds. If you change
+# this, you must update your /etc/make.profile symlink accordingly.
+PORTDIR=/usr/portage
+#
+# DISTDIR is where all of the source code tarballs will be placed for
+# emerges. The source code is maintained here unless you delete
+# it. The entire repository of tarballs for gentoo is 9G. This is
+# considerably more than any user will ever download. 2-3G is
+# a large DISTDIR.
+DISTDIR=/usr/distfiles
+#
+# PKGDIR is the location of binary packages that you can have created
+# with '--buildpkg' or '-b' while emerging a package. This can get
+# upto several hundred megs, or even a few gigs.
+#PKGDIR=${PORTDIR}/packages
+#
+# PORT_LOGDIR is the location where portage will store all the logs it
+# creates from each individual merge. They are stored as NNNN-$PF.log
+# in the directory specified. This is disabled until you enable it by
+# providing a directory. Permissions will be modified as needed IF the
+# directory exists, otherwise logging will be disabled. NNNN is the
+# increment at the time the log is created. Logs are thus sequential.
+PORT_LOGDIR=/var/log/services/portage.d
+#
+# PORTDIR_OVERLAY is a directory where local ebuilds may be stored without
+# concern that they will be deleted by rsync updates. Default is not
+# defined.
+PORTDIR_OVERLAY="
+/var/lib/layman/wrobel-stable
+$PORTDIR_OVERLAY
+/usr/local/portage/ebuilds/testing
+/usr/local/portage/ebuilds/stable
+/usr/local/portage/kolab2
+/usr/local/portage/gentoo-webapps-overlay/experimental
+/usr/local/portage/gentoo-webapps-overlay/production-ready"
+
+# Fetching files
+# ==============
+#
+# If you need to set a proxy for wget or lukemftp, add the appropriate "export
+# ftp_proxy=<proxy>" and "export http_proxy=<proxy>" lines to /etc/profile if
+# all users on your system should use them.
+#
+# Portage uses wget by default. Here are some settings for some alternate
+# downloaders -- note that you need to merge these programs first before they
+# will be available.
+#
+# Default fetch command (5 tries, passive ftp for firewall compatibility)
+#FETCHCOMMAND="/usr/bin/wget -t 5 --passive-ftp \${URI} -P \${DISTDIR}"
+#RESUMECOMMAND="/usr/bin/wget -c -t 5 --passive-ftp \${URI} -P \${DISTDIR}"
+#
+# Using wget, ratelimiting downloads
+#FETCHCOMMAND="/usr/bin/wget -t 5 --passive-ftp --limit-rate=200k \${URI} -P \${DISTDIR}"
+#RESUMECOMMAND="/usr/bin/wget -c -t 5 --passive-ftp --limit-rate=200k \${URI} -P \${DISTDIR}"
+#
+# Lukemftp (BSD ftp):
+#FETCHCOMMAND="/usr/bin/lukemftp -s -a -o \${DISTDIR}/\${FILE} \${URI}"
+#RESUMECOMMAND="/usr/bin/lukemftp -s -a -R -o \${DISTDIR}/\${FILE} \${URI}"
+#
+
+FETCHCOMMAND="/usr/bin/getdelta.sh \${URI}"
+
+
+# Portage uses GENTOO_MIRRORS to specify mirrors to use for source retrieval.
+# The list is a space separated list which is read left to right. If you use
+# another mirror we highly recommend leaving the default mirror at the end of
+# the list so that portage will fall back to it if the files cannot be found
+# on your specified mirror. We _HIGHLY_ recommend that you change this setting
+# to a nearby mirror by merging and using the 'mirrorselect' tool.
+
+GENTOO_MIRRORS="http://pandemonium.tiscali.de/pub/gentoo/ ftp://pandemonium.tiscali.de/pub/gentoo/ ftp://ftp-stud.fht-esslingen.de/pub/Mirrors/gentoo/ http://mir.zyrianes.net/gentoo/ http://ftp.snt.utwente.nl/pub/os/linux/gentoo http://distfiles.gentoo.org http://www.ibiblio.org/pub/Linux/distributions/gentoo"
+
+#
+# Portage uses PORTAGE_BINHOST to specify mirrors for prebuilt-binary packages.
+# The list is a single entry specifying the full address of the directory
+# serving the tbz2's for your system. Running emerge with either '--getbinpkg'
+# or '--getbinpkgonly' will cause portage to retrieve the metadata from all
+# packages in the directory specified, and use that data to determine what will
+# be downloaded and merged. '-g' or '-gK' are the recommend parameters. Please
+# consult the man pages and 'emerge --help' for more information. For FTP, the
+# default connection is passive -- If you require an active connection, affix
+# an asterisk (*) to the end of the host:port string before the path.
+#PORTAGE_BINHOST="http://grp.mirror.site/gentoo/grp/1.4/i686/athlon-xp/"
+# This ftp connection is passive ftp.
+#PORTAGE_BINHOST="ftp://login:pass@grp.mirror.site/pub/grp/i686/athlon-xp/"
+# This ftp connection is active ftp.
+#PORTAGE_BINHOST="ftp://login:pass@grp.mirror.site:21*/pub/grp/i686/athlon-xp/"
+
+# Synchronizing Portage
+# =====================
+#
+# Each of these settings affects how Gentoo synchronizes your Portage tree.
+# Synchronization is handled by rsync and these settings allow some control
+# over how it is done.
+#
+#
+# SYNC is the server used by rsync to retrieve a localized rsync mirror
+# rotation. This allows you to select servers that are geographically
+# close to you, yet still distribute the load over a number of servers.
+# Please do not single out specific rsync mirrors. Doing so places undue
+# stress on particular mirrors. Instead you may use one of the following
+# continent specific rotations:
+#
+# Default: "rsync://rsync.gentoo.org/gentoo-portage"
+# North America: "rsync://rsync.namerica.gentoo.org/gentoo-portage"
+# South America: "rsync://rsync.samerica.gentoo.org/gentoo-portage"
+# Europe: "rsync://rsync.europe.gentoo.org/gentoo-portage"
+# Asia: "rsync://rsync.asia.gentoo.org/gentoo-portage"
+# Australia: "rsync://rsync.au.gentoo.org/gentoo-portage"
+
+SYNC="rsync://rsync.europe.gentoo.org/gentoo-portage"
+
+#
+# RSYNC_RETRIES sets the number of times portage will attempt to retrieve
+# a current portage tree before it exits with an error. This allows
+# for a more successful retrieval without user intervention most times.
+#RSYNC_RETRIES="3"
+#
+# RSYNC_TIMEOUT sets the length of time rsync will wait before it times out
+# on a connection. Most users will benefit from this setting as it will
+# reduce the amount of 'dead air' they experience when they run across
+# the occasional, unreachable mirror. Dialup users might want to set this
+# value up around the 300 second mark.
+#RSYNC_TIMEOUT=180
+
+# Advanced Features
+# =================
+#
+# MAKEOPTS provides extra options that may be passed to 'make' when a
+# program is compiled. Presently the only use is for specifying
+# the number of parallel makes (-j) to perform. The suggested number
+# for parallel makes is CPUs+1.
+MAKEOPTS="-j2"
+#
+# PORTAGE_NICENESS provides a default increment to emerge's niceness level.
+# Note: This is an increment. Running emerge in a niced environment will
+# reduce it further. Default is unset.
+PORTAGE_NICENESS=3
+#
+# AUTOCLEAN enables portage to automatically clean out older or overlapping
+# packages from the system after every successful merge. This is the
+# same as running 'emerge -c' after every merge. Set with: "yes" or "no".
+# This does not affect the unpacked source. See 'noclean' below.
+AUTOCLEAN="yes"
+#
+# PORTAGE_TMPFS is a location where portage may create temporary files.
+# If specified, portage will use this directory whenever possible
+# for all rapid operations such as lockfiles and transient data.
+# It is _highly_ recommended that this be a tmpfs or ramdisk. Do not
+# set this to anything that does not give a significant performance
+# enhancement and proper FS compliance for locks and read/write.
+# /dev/shm is a glibc mandated tmpfs, and should be a reasonable
+# setting for all linux kernel+glibc based systems.
+#PORTAGE_TMPFS="/dev/shm"
+#
+# FEATURES are settings that affect the functionality of portage. Most of
+# these settings are for developer use, but some are available to non-
+# developers as well.
+#
+# 'autoaddcvs' causes portage to automatically try to add files to cvs
+# that will have to be added later. Done at generation times
+# and only has an effect when 'cvs' is also set.
+# 'buildpkg' causes binary packages to be created of all packages that
+# are being merged.
+# 'ccache' enables ccache support via CC.
+# 'collision-protect'
+# prevents packages from overwriting files that are owned by
+# another package or by no package at all.
+# 'cvs' causes portage to enable all cvs features (commits, adds),
+# and to apply all USE flags in SRC_URI for digests -- for
+# developers only.
+# 'digest' causes digests to be generated for all packages being merged.
+# 'distcc' enables distcc support via CC.
+# 'distlocks' enables distfiles locking using fcntl or hardlinks. This
+# is enabled by default. Tools exist to help clean the locks
+# after crashes: /usr/lib/portage/bin/clean_locks.
+# 'fixpackages' allows portage to fix binary packages that are stored in
+# PKGDIR. This can consume a lot of time. 'fixpackages' is
+# also a script that can be run at any given time to force
+# the same actions.
+# 'gpg' enables basic verification of Manifest files using gpg.
+# This features is UNDER DEVELOPMENT and reacts to features
+# of strict and severe. Heavy use of gpg sigs is coming.
+# 'keeptemp' prevents the clean phase from deleting the temp files ($T)
+# from a merge.
+# 'keepwork' prevents the clean phase from deleting the WORKDIR.
+# 'maketest' causes ebuilds to perform testing phases if they are capable
+# of it. Some packages support this automaticaly via makefiles.
+# 'noauto' causes ebuild to perform only the action requested and
+# not any other required actions like clean or unpack -- for
+# debugging purposes only.
+# 'noclean' prevents portage from removing the source and temporary files
+# after a merge -- for debugging purposes only.
+# 'nostrip' prevents the stripping of binaries.
+# 'notitles' disables xterm titlebar updates (which contain status info).
+# 'sandbox' enables sandboxing when running emerge and ebuild.
+# 'strict' causes portage to react strongly to conditions that are
+# potentially dangerous, like missing/incorrect Manifest files.
+# 'userpriv' allows portage to drop root privileges while it is compiling,
+# as a security measure. As a side effect this can remove
+# sandbox access violations for users.
+# 'usersandbox' enables sandboxing while portage is running under userpriv.
+#FEATURES="sandbox buildpkg ccache distcc userpriv usersandbox notitles noclean noauto cvs keeptemp keepwork autoaddcvs"
+FEATURES="sandbox ccache userprivs distlocks cvs"
+#
+# CCACHE_SIZE sets the space use limitations for ccache. The default size is
+# 2G, and will be set if not defined otherwise and ccache is in features.
+# Portage will set the default ccache dir if it is not present in the
+# user's environment, for userpriv it sets: ${PORTAGE_TMPDIR}/ccache
+# (/var/tmp/ccache), and for regular use the default is /root/.ccache.
+# Sizes are specified with 'G' 'M' or 'K'.
+# '2G' for 2 gigabytes, '2048M' for 2048 megabytes (same as 2G).
+CCACHE_SIZE="1G"
+#
+# DISTCC_DIR sets the temporary space used by distcc.
+#DISTCC_DIR="${PORTAGE_TMPDIR}/.distcc"
+#
+# RSYNC_EXCLUDEFROM is a file that portage will pass to rsync when it updates
+# the portage tree. Specific chunks of the tree may be excluded from
+# consideration. This may cause dependency failures if you are not careful.
+# The file format is one pattern per line, blanks and ';' or '#' lines are
+# comments. See 'man rsync' for more details on the exclude-from format.
+#RSYNC_EXCLUDEFROM=/etc/portage/rsync_excludes
+EBEEP_IGNORE=yes
+
+CONFIG_PROTECT_MASK="/usr/X11R6/lib/X11"
+
+
diff --git a/overlord/tests/testfiles/overlays_bug_184449.xml b/overlord/tests/testfiles/overlays_bug_184449.xml
new file mode 100644
index 0000000..c8dff2d
--- /dev/null
+++ b/overlord/tests/testfiles/overlays_bug_184449.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" ?>
+<layman>
+
+ <overlay
+ type = "svn"
+ src = "https://overlays.gentoo.org/svn/dev/wrobel"
+ contact = "nobody@gentoo.org"
+ name = "wrÖbel"
+ status = "official"
+ priority = "10">
+
+ <description>
+ Test ä
+ </description>
+
+ </overlay>
+
+
+</layman>
diff --git a/overlord/tests/testfiles/overlays_bug_286290.xml b/overlord/tests/testfiles/overlays_bug_286290.xml
new file mode 100644
index 0000000..1d4bd1b
--- /dev/null
+++ b/overlord/tests/testfiles/overlays_bug_286290.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<layman>
+ <overlay contact="media-video@gentoo.org"
+ name="multimedia"
+ src="git://gitorious.org/gentoo-multimedia/gentoo-multimedia.git"
+ status="official"
+ type="git">
+ <link>http://gitorious.org/gentoo-multimedia</link>
+ <description>Repository for development of (mostly bleeding-edge)
+ multimedia packages for Gentoo Linux. This is the official overlay
+ for Gentoo’s media herds.</description>
+ </overlay>
+</layman>
diff --git a/overlord/tests/testfiles/subpath-1.xml b/overlord/tests/testfiles/subpath-1.xml
new file mode 100644
index 0000000..ddb0e3e
--- /dev/null
+++ b/overlord/tests/testfiles/subpath-1.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<layman>
+ <overlay
+ name="b_name"
+ contact="b_owner@example.org"
+ type="tar"
+ src="http://example.org/b.tar.gz"
+ subpath="b_path">
+ <description>b_desc</description>
+ </overlay>
+ <overlay
+ name="c_name"
+ contact="c_owner@example.org"
+ type="cvs"
+ src=":pserver:username@example.org:/usr/local/cvs-repository"
+ subpath="c_path">
+ <description>c_desc</description>
+ </overlay>
+</layman>
diff --git a/overlord/tests/testfiles/subpath-2.xml b/overlord/tests/testfiles/subpath-2.xml
new file mode 100644
index 0000000..aa11497
--- /dev/null
+++ b/overlord/tests/testfiles/subpath-2.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE repositories SYSTEM "/dtd/repositories.dtd">
+<repositories xmlns="" version="1.0">
+ <repo>
+ <name>b_name</name>
+ <description>b_desc</description>
+ <owner>
+ <email>b_owner@example.org</email>
+ </owner>
+ <source type="tar">http://example.org/b.tar.gz</source>
+ <subpath>b_path</subpath>
+ </repo>
+ <repo>
+ <name>c_name</name>
+ <description>c_desc</description>
+ <owner>
+ <email>c_owner@example.org</email>
+ </owner>
+ <source type="cvs">:pserver:username@example.org:/usr/local/cvs-repository</source>
+ <subpath>c_path</subpath>
+ </repo>
+</repositories>
diff --git a/overlord/utils.py b/overlord/utils.py
new file mode 100644
index 0000000..c5a2019
--- /dev/null
+++ b/overlord/utils.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# POLYMERAZE XML UTILITIES
+#################################################################################
+# File: xml.py
+#
+# Utilities to deal with xml nodes.
+#
+# Copyright:
+# (c) 2005 - 2008 Gunnar Wrobel
+# (c) 2009 Sebastian Pipping
+# (c) 2009 Christian Groschupp
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Sebastian Pipping <sebastian@pipping.org>
+# Christian Groschupp <christian@groschupp.org>
+#
+
+'''Utility functions to deal with xml nodes. '''
+
+__version__ = '$Id: utils.py 236 2006-09-05 20:39:37Z wrobel $'
+
+#===============================================================================
+#
+# Dependencies
+#
+#-------------------------------------------------------------------------------
+
+import types, re, os
+import sys
+import locale
+import codecs
+
+from overlord.debug import OUT
+
+
+#===============================================================================
+#
+# Helper functions
+#
+#-------------------------------------------------------------------------------
+
+def decode_selection(selection):
+ '''utility function to decode a list of strings
+ accoring to the filesystem encoding
+ '''
+ # fix None passed in, return an empty list
+ selection = selection or []
+ enc = sys.getfilesystemencoding()
+ if enc:
+ return [i.decode(enc) for i in selection]
+ return selection
+
+
+def encoder(unicode_text, _encoding_):
+ return codecs.encode(unicode_text, _encoding_, 'replace')
+
+
+def get_encoding(output):
+ if hasattr(output, 'encoding') \
+ and output.encoding != None:
+ return output.encoding
+ else:
+ return locale.getpreferredencoding()
+
+
+def pad(string, length):
+ '''Pad a string with spaces.'''
+ if len(string) <= length:
+ return string + ' ' * (length - len(string))
+ else:
+ return string[:length - 3] + '...'
+
+
+def terminal_width():
+ '''Determine width of terminal window.'''
+ try:
+ width = int(os.environ['COLUMNS'])
+ if width > 0:
+ return width
+ except:
+ pass
+ try:
+ import struct, fcntl, termios
+ query = struct.pack('HHHH', 0, 0, 0, 0)
+ response = fcntl.ioctl(1, termios.TIOCGWINSZ, query)
+ width = struct.unpack('HHHH', response)[1]
+ if width > 0:
+ return width
+ except:
+ pass
+ return 80
+
+
+def ensure_unicode(obj, encoding='utf-8'):
+ if isinstance(obj, basestring):
+ if not isinstance(obj, unicode):
+ obj = unicode(obj, encoding)
+ return obj
+
+# From <http://effbot.org/zone/element-lib.htm>
+# BEGIN
+def indent(elem, level=0):
+ i = "\n" + level*" "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+# END
+
+def path(path_elements):
+ '''
+ Concatenate a path from several elements.
+
+ >>> path([])
+ ''
+ >>> path(['a'])
+ 'a'
+ >>> path(['a','b'])
+ 'a/b'
+ >>> path(['a/','b'])
+ 'a/b'
+ >>> path(['/a/','b'])
+ '/a/b'
+ >>> path(['/a/','b/'])
+ '/a/b'
+ >>> path(['/a/','b/'])
+ '/a/b'
+ >>> path(['/a/','/b/'])
+ '/a/b'
+ >>> path(['/a/','/b','c/'])
+ '/a/b/c'
+ '''
+ pathname = ''
+
+ if type(path_elements) in types.StringTypes:
+ path_elements = [path_elements]
+
+ # Concatenate elements and seperate with /
+ for i in path_elements:
+ pathname += i + '/'
+
+ # Replace multiple consecutive slashes
+ pathname = re.compile('/+').sub('/', pathname)
+
+ # Remove the final / if there is one
+ if pathname and pathname[-1] == '/':
+ pathname = pathname[:-1]
+
+ return pathname
+
+def delete_empty_directory(mdir, output=OUT):
+ if os.path.exists(mdir) and not os.listdir(mdir):
+ # Check for sufficient privileges
+ if os.access(mdir, os.W_OK):
+ output.info('Deleting _empty_ directory "%s"' % mdir, 2)
+ try:
+ os.rmdir(mdir)
+ except OSError, error:
+ output.warn(str(error))
+ else:
+ output.warn('Insufficient permissions to delete _empty_ folder "%s".' % mdir)
+ import getpass
+ if getpass.getuser() != 'root':
+ output.warn('Hint: You are not root.')
+
+#===============================================================================
+#
+# Testing
+#
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/overlord/version.py b/overlord/version.py
new file mode 100644
index 0000000..abbea1a
--- /dev/null
+++ b/overlord/version.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#################################################################################
+# overlord VERSION
+#################################################################################
+# File: version.py
+#
+# Current version number
+#
+# Copyright:
+# (c) 2005 - 2009 Gunnar Wrobel
+# (c) 2009 Sebastian Pipping
+# Distributed under the terms of the GNU General Public License v2
+#
+# Author(s):
+# Gunnar Wrobel <wrobel@gentoo.org>
+# Sebastian Pipping <sebastian@pipping.org>
+#
+
+__version__ = "$Id: version.py 309 2007-04-09 16:23:38Z wrobel $"
+
+
+VERSION = '1.4.1-API'
+
+if __name__ == '__main__':
+ print VERSION