Source code for repobuddy.command_handler

#
#   Copyright (C) 2013 Ash (Tuxdude) <tuxdude.github@gmail.com>
#
#   This file is part of repobuddy.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
"""
.. module: repobuddy.command_handler
   :platform: Unix, Windows
   :synopsis: Parses/Stores/Retrieves client specific configuration.
.. moduleauthor: Ash <tuxdude.github@gmail.com>

"""

import os as _os
import shutil as _shutil

from repobuddy.git_wrapper import GitWrapper, GitWrapperError
from repobuddy.utils import FileLock, FileLockError, Logger, \
    RepoBuddyBaseException
from repobuddy.manifest_parser import ManifestParser, ManifestParserError
from repobuddy.client_info import ClientInfo, ClientInfoError


[docs]class CommandHandlerError(RepoBuddyBaseException): """Exception raised by :class:`CommandHandler`."""
[docs] def __init__(self, error_str): super(CommandHandlerError, self).__init__(error_str) return
[docs]class CommandHandler(object): """Provides handlers for the ``repobuddy`` commands.""" def _get_manifest(self, manifest): """Retrieve the ``manifest`` into ``.repobuddy`` directory. If ``manifest`` is a file, it is copied into the ``.repobuddy`` directory. As of now, the ``manifest`` argument needs to be a filename. Other protocols for fetching the manifest might be supported in the future. :param manifest: Manifest to retrieve. :type manifest: str :returns: None :raises: :exc:`CommandHandlerError` if unable to open the manifest. """ # FIXME: Support various protcols for fetching the manifest XML file input_manifest = None if not _os.path.isabs(manifest): input_manifest = _os.path.join(self._current_dir, manifest) else: input_manifest = manifest # Copy the manifest xml file to .repobuddy dir try: _shutil.copyfile(input_manifest, self._manifest_file) except IOError as err: raise CommandHandlerError('Error: ' + str(err)) return def _parse_manifest(self): """Parse the ``manifest``. :returns: None :raises: :exc:`CommandHandlerError` on parsing errors. """ manifest_parser = ManifestParser() try: manifest_parser.parse(open(self._manifest_file, 'r')) except ManifestParserError as err: raise CommandHandlerError(str(err)) self._manifest = manifest_parser.get_manifest() return def _get_client_spec(self, client_spec_name): """Retrieve the ``client_spec``. :param client_spec_name: The name of the client_spec. :type client_spec_name: str :returns: Client Spec :rtype: :class:`repobuddy.manifest_parser.ClientSpec` :raises: :exc:`CommandHandlerError` """ client_spec = None for spec in self._manifest.client_spec_list: if spec.name == client_spec_name: client_spec = spec break if client_spec is None: raise CommandHandlerError( 'Error: Unable to find the Client Spec: \'' + client_spec_name + '\'') return client_spec def _store_client_info(self, client_spec_name): """Write the client config to ``.repobuddy/client.config``. :param client_spec_name: Name of the client_spec. :type client_spec: str :returns: None :raises: :exc:`CommandHandlerError` on any failures in creating or storing the config. """ try: client_info = ClientInfo() client_info.set_client_spec(client_spec_name) client_info.set_manifest('manifest.xml') client_info.write(self._client_info_file) except ClientInfoError as err: raise CommandHandlerError(str(err)) return def _get_client_spec_name_from_config(self): """Retrieve the client_spec name from the client config. :returns: Value of client_spec in the config. :rtype: str :raises: :exc:`CommandHandlerError` on any failures. """ try: client_info = ClientInfo(self._client_info_file) return client_info.get_client_spec() except ClientInfoError as err: raise CommandHandlerError(str(err)) return def _is_client_initialized(self): """Determine if the client is initialized. :returns: ``True`` is the client is initialized, ``False`` otherwise. :rtype: Boolean """ return _os.path.isfile( _os.path.join(self._repo_buddy_dir, 'client.config')) def _exec_with_lock(self, exec_method, *method_args): """Call ``exec_method`` while holding the ``.repobuddy/lock``. :param exec_method: The method to execute. :type exec_method: Reference to a method :param method_args: Arguments to the method. :type method_args: list :returns: None :raises: :exc:`CommandHandlerError` if failing to create the lock file or any errors in executing the method. """ lock_file = _os.path.join(self._repo_buddy_dir, 'lock') if not _os.path.isdir(self._repo_buddy_dir): # Create the .repobuddy directory if it does not exist already try: _os.mkdir(self._repo_buddy_dir) except OSError as err: raise CommandHandlerError('Error: ' + str(err)) else: Logger.debug('Found an existing .repobuddy directory...') try: # Acquire the lock before doing anything else with FileLock(lock_file): Logger.debug('Lock \'' + lock_file + '\' acquired') exec_method(*method_args) except FileLockError as err: # If it is a timeout error, it could be one of the following: # *** another instance of repobuddy is running # *** repobuddy was killed earlier without releasing the lock file if err.isTimeOut: raise CommandHandlerError( 'Error: Lock file ' + lock_file + ' already exists\n' + 'Is another instance of repobuddy running ?') else: raise CommandHandlerError(str(err)) except GitWrapperError as err: raise CommandHandlerError('Error: Git said => ' + str(err)) return # Init command which runs after acquiring the Lock def _exec_init(self, args): """Execute ``init`` command. This method needs to be called after acquiring the lock. :param args: Arguments to the init command. :type args: Namespace containing the arguments. :returns: None :raises: :exc:`CommandHandlerError` on any errors. """ if self._is_client_initialized(): raise CommandHandlerError('Error: Client is already initialized') # Download the manifest XML self._get_manifest(args.manifest) # Parse the manifest XML self._parse_manifest() # Get the Client Spec corresponding to the Command line argument client_spec = self._get_client_spec(args.client_spec) # Process each repo in the Client Spec for repo in client_spec.repo_list: git = GitWrapper(self._current_dir) git.clone(repo.url, repo.branch, repo.dest) # Create the client file, writing the following # The manifest file name # The client spec chosen self._store_client_info(args.client_spec) return def _exec_status(self): """Execute the ``status`` command. This method needs to be called after acquiring the lock. :returns: None :raises: :exc:`CommandHandlerError` on errors. """ if not self._is_client_initialized(): raise CommandHandlerError( 'Error: Uninitialized client, ' + 'please run init to initialize the client first') # Parse the manifest XML self._parse_manifest() # Get the client spec name from client info client = self._get_client_spec( self._get_client_spec_name_from_config()) # Process each repo in the Client Spec for repo in client.repo_list: git = GitWrapper( _os.path.join(self._current_dir, repo.dest)) Logger.msg('####################################################') Logger.msg('Repo: ' + repo.dest) Logger.msg('Remote URL: ' + repo.url) git.update_index() current_branch = git.get_current_branch() dirty = False if current_branch is None: current_branch = 'Detached HEAD' if current_branch != repo.branch: Logger.msg('Original Branch: ' + repo.branch) Logger.msg('Current Branch: ' + current_branch + '\n') else: Logger.msg('Branch: ' + repo.branch + '\n') untracked_files = git.get_untracked_files() if len(untracked_files) != 0: Logger.msg( 'Untracked Files: \n' + '\n'.join(untracked_files) + '\n') dirty = True unstaged_files = git.get_unstaged_files() if len(unstaged_files) != 0: Logger.msg( 'Unstaged Files: \n' + '\n'.join(unstaged_files) + '\n') dirty = True uncommitted_staged_files = git.get_uncommitted_staged_files() if len(uncommitted_staged_files) != 0: Logger.msg('Uncommitted Changes: \n' + '\n'.join(uncommitted_staged_files) + '\n') dirty = True if not dirty: Logger.msg('No uncommitted changes') Logger.msg('####################################################') return
[docs] def __init__(self): """Initializer.""" self._manifest = None self._current_dir = _os.getcwd() self._repo_buddy_dir = _os.path.join(self._current_dir, '.repobuddy') self._manifest_file = _os.path.join(self._repo_buddy_dir, 'manifest.xml') self._client_info_file = _os.path.join( self._repo_buddy_dir, 'client.config') return
[docs] def get_handlers(self): """Get the command handlers. :returns: Dictionary with command names as keys and the methods as values. :rtype: dict """ handlers = {} handlers['init'] = self.init_command_handler handlers['status'] = self.status_command_handler return handlers
[docs] def init_command_handler(self, args): """Handler for the ``init`` command. :returns: None :raises: :exc:`CommandHandlerError` on errors. """ self._exec_with_lock(self._exec_init, args) return
[docs] def status_command_handler(self, _args): """Handler for the ``status`` command. :returns: None :raises: :exc:`CommandHandlerError` on errors. """ self._exec_with_lock(self._exec_status) return