コード例 #1
0
class ClowderRepoTest(unittest.TestCase):
    """clowder_repo test subclass"""

    current_file_path = os.path.dirname(os.path.realpath(__file__))
    cats_example_path = os.path.abspath(
        os.path.join(current_file_path, '..', '..', 'examples', 'cats'))

    def setUp(self):

        self.clowder_repo = ClowderRepo()
        self.clowder_yaml_path = os.path.join(self.cats_example_path,
                                              'clowder.yaml')

    def test_member_variables(self):
        """Test the state of all project member variables initialized"""

        clowder_path = os.path.join(self.cats_example_path, '.clowder')
        self.assertEqual(self.clowder_repo.clowder_path, clowder_path)

    def test_link(self):
        """Test link() method"""

        self.clowder_repo.link()
        self.assertEqual(
            os.readlink(self.clowder_yaml_path),
            os.path.join(self.cats_example_path, '.clowder', 'clowder.yaml'))

    def test_link_version(self):
        """Test link() method"""

        self.clowder_repo.link('v0.1')
        version_path = os.path.join('.clowder', 'versions', 'v0.1',
                                    'clowder.yaml')
        self.assertEqual(os.readlink(self.clowder_yaml_path),
                         os.path.join(self.cats_example_path, version_path))
コード例 #2
0
ファイル: test_clowder_repo.py プロジェクト: JrGoodle/clowder
class ClowderRepoTest(unittest.TestCase):
    """clowder_repo test subclass"""
    def setUp(self):
        self.clowder_repo = ClowderRepo(CATS_EXAMPLE_PATH)
        self.clowder_yaml_path = os.path.join(CATS_EXAMPLE_PATH, 'clowder.yaml')

    def test_member_variables(self):
        """Test the state of all project member variables initialized"""
        self.assertEqual(self.clowder_repo.root_directory, CATS_EXAMPLE_PATH)
        clowder_path = os.path.join(CATS_EXAMPLE_PATH, '.clowder')
        self.assertEqual(self.clowder_repo.clowder_path, clowder_path)

    def test_link(self):
        """Test link() method"""
        self.clowder_repo.link()
        self.assertEqual(os.readlink(self.clowder_yaml_path),
                         os.path.join(CATS_EXAMPLE_PATH, '.clowder', 'clowder.yaml'))

    def test_link_version(self):
        """Test link() method"""
        self.clowder_repo.link('v0.1')
        version_path = os.path.join('.clowder', 'versions', 'v0.1', 'clowder.yaml')
        self.assertEqual(os.readlink(self.clowder_yaml_path),
                         os.path.join(CATS_EXAMPLE_PATH, version_path))
コード例 #3
0
ファイル: cmd.py プロジェクト: Cameron-C-Chapman/clowder
class Command(object):
    """Command class for parsing commandline options"""
    def __init__(self):
        self.root_directory = os.getcwd()
        self.clowder = None
        self.clowder_repo = None
        self.versions = None
        self._invalid_yaml = False
        self._version = '2.4.0'
        clowder_path = os.path.join(self.root_directory, '.clowder')

        # Load current clowder.yaml config if it exists
        if os.path.isdir(clowder_path):
            clowder_symlink = os.path.join(self.root_directory, 'clowder.yaml')
            self.clowder_repo = ClowderRepo(self.root_directory)
            if not os.path.islink(clowder_symlink):
                print()
                clowder_output = colored('.clowder', 'green')
                print(clowder_output)
                self.clowder_repo.link()
            try:
                self.clowder = ClowderController(self.root_directory)
                self.versions = self.clowder.get_saved_version_names()
            except (ClowderError, KeyError) as err:
                self._invalid_yaml = True
                self._error = err
            except (KeyboardInterrupt, SystemExit):
                sys.exit(1)

        # clowder argparse setup
        command_description = 'Utility for managing multiple git repositories'
        parser = argparse.ArgumentParser(
            description=command_description,
            formatter_class=argparse.RawDescriptionHelpFormatter)
        configure_argparse(parser, self.clowder, self.versions)

        # Argcomplete and arguments parsing
        argcomplete.autocomplete(parser)

        # Register exit handler to display trailing newline
        self._display_trailing_newline = True
        atexit.register(self._exit_handler_formatter)
        if not self._invalid_yaml:
            print()
        self.args = parser.parse_args()
        self._display_trailing_newline = False

        # Check for unrecognized command
        if self.args.clowder_command is None or not hasattr(
                self, self.args.clowder_command):
            exit_unrecognized_command(parser)

        # use dispatch pattern to invoke method with same name
        getattr(self, self.args.clowder_command)()
        print()

    def branch(self):
        """clowder branch command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        if self.args.all:
            self.clowder.branch(group_names=self.args.groups,
                                project_names=self.args.projects,
                                skip=self.args.skip,
                                local=True,
                                remote=True)
            return

        if self.args.remote:
            self.clowder.branch(group_names=self.args.groups,
                                project_names=self.args.projects,
                                skip=self.args.skip,
                                remote=True)
            return

        self.clowder.branch(group_names=self.args.groups,
                            project_names=self.args.projects,
                            skip=self.args.skip,
                            local=True)

    def clean(self):
        """clowder clean command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        if self.args.all:
            self.clowder.clean_all(group_names=self.args.groups,
                                   project_names=self.args.projects,
                                   skip=self.args.skip)
            return

        clean_args = ''
        if self.args.d:
            clean_args += 'd'
        if self.args.f:
            clean_args += 'f'
        if self.args.X:
            clean_args += 'X'
        if self.args.x:
            clean_args += 'x'
        self.clowder.clean(group_names=self.args.groups,
                           project_names=self.args.projects,
                           skip=self.args.skip,
                           args=clean_args,
                           recursive=self.args.recursive)

    def diff(self):
        """clowder diff command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        self.clowder.diff(group_names=self.args.groups,
                          project_names=self.args.projects)

    def forall(self):
        """clowder forall command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        self.clowder.forall(self.args.command[0],
                            self.args.ignore_errors,
                            group_names=self.args.groups,
                            project_names=self.args.projects,
                            skip=self.args.skip,
                            parallel=self.args.parallel)

    def herd(self):
        """clowder herd command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status(fetch=True)
        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        if self.clowder is None:
            sys.exit(1)

        branch = None if self.args.branch is None else self.args.branch[0]
        tag = None if self.args.tag is None else self.args.tag[0]
        depth = None if self.args.depth is None else self.args.depth[0]

        args = {
            'group_names': self.args.groups,
            'project_names': self.args.projects,
            'skip': self.args.skip,
            'branch': branch,
            'tag': tag,
            'depth': depth,
            'rebase': self.args.rebase
        }
        if self.args.parallel:
            self.clowder.herd_parallel(**args)
            return
        self.clowder.herd(**args)

    def init(self):
        """clowder init command"""

        if self.clowder_repo:
            cprint('Clowder already initialized in this directory\n', 'red')
            sys.exit(1)

        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        url_output = colored(self.args.url, 'green')
        print('Create clowder repo from ' + url_output + '\n')
        clowder_repo = ClowderRepo(self.root_directory)
        if self.args.branch is None:
            branch = 'master'
        else:
            branch = str(self.args.branch[0])
        clowder_repo.init(self.args.url, branch)

    def link(self):
        """clowder link command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.args.version is None:
            version = None
        else:
            version = self.args.version[0]
        self.clowder_repo.link(version)

    def prune(self):
        """clowder prune command"""
        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        if self.args.all:
            if is_offline():
                print(fmt.offline_error())
                sys.exit(1)

            self.clowder.prune(self.args.groups,
                               self.args.branch,
                               project_names=self.args.projects,
                               skip=self.args.skip,
                               force=self.args.force,
                               local=True,
                               remote=True)
            return

        if self.args.remote:
            if is_offline():
                print(fmt.offline_error())
                sys.exit(1)

            self.clowder.prune(self.args.groups,
                               self.args.branch,
                               project_names=self.args.projects,
                               skip=self.args.skip,
                               remote=True)
            return

        self.clowder.prune(self.args.groups,
                           self.args.branch,
                           project_names=self.args.projects,
                           skip=self.args.skip,
                           force=self.args.force,
                           local=True)

    def repo(self):
        """clowder repo command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        repo_command = 'repo_' + self.args.repo_command
        getattr(self, repo_command)()

    def repo_add(self):
        """clowder repo add command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        self.clowder_repo.add(self.args.files)

    def repo_checkout(self):
        """clowder repo checkout command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status(fetch=True)
        self.clowder_repo.checkout(self.args.ref[0])

    def repo_clean(self):
        """clowder repo clean command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        self.clowder_repo.clean()

    def repo_commit(self):
        """clowder repo commit command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        self.clowder_repo.commit(self.args.message[0])

    def repo_pull(self):
        """clowder repo pull command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        self.clowder_repo.print_status(fetch=True)
        self.clowder_repo.pull()

    def repo_push(self):
        """clowder repo push command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        self.clowder_repo.print_status(fetch=True)
        self.clowder_repo.push()

    def repo_run(self):
        """clowder repo run command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        self.clowder_repo.run_command(self.args.command[0])

    def repo_status(self):
        """clowder repo status command"""
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        self.clowder_repo.git_status()

    def reset(self):
        """clowder reset command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status(fetch=True)
        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        if self.clowder is None:
            sys.exit(1)

        timestamp_project = None
        if self.args.timestamp:
            timestamp_project = self.args.timestamp[0]
        self.clowder.reset(group_names=self.args.groups,
                           project_names=self.args.projects,
                           skip=self.args.skip,
                           timestamp_project=timestamp_project,
                           parallel=self.args.parallel)

    def save(self):
        """clowder save command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        if self.clowder is None:
            sys.exit(1)

        if self.args.version.lower() == 'default':
            print(fmt.save_default_error(self.args.version))
            sys.exit(1)

        self.clowder_repo.print_status()
        self.clowder.save_version(self.args.version)

    def start(self):
        """clowder start command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        if self.args.tracking:
            if is_offline():
                print(fmt.offline_error())
                sys.exit(1)

        if self.args.projects is None:
            self.clowder.start_groups(self.args.groups, self.args.skip,
                                      self.args.branch, self.args.tracking)
        else:
            self.clowder.start_projects(self.args.projects, self.args.skip,
                                        self.args.branch, self.args.tracking)

    def stash(self):
        """clowder stash command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self.clowder is None:
            sys.exit(1)

        self.clowder.stash(group_names=self.args.groups,
                           project_names=self.args.projects,
                           skip=self.args.skip)

    def status(self):
        """clowder status command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status(fetch=self.args.fetch)
        if self.clowder is None:
            sys.exit(1)

        if self.args.fetch:
            if is_offline():
                print(fmt.offline_error())
                sys.exit(1)

            print(' - Fetch upstream changes for projects\n')
            self.clowder.fetch(self.clowder.get_all_group_names())

        all_project_paths = self.clowder.get_all_project_paths()
        padding = len(max(all_project_paths, key=len))
        self.clowder.status(self.clowder.get_all_group_names(), padding)

    def sync(self):
        """clowder sync command"""

        self._validate_clowder_yaml()
        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status(fetch=True)
        if self.clowder is None:
            sys.exit(1)

        if is_offline():
            print(fmt.offline_error())
            sys.exit(1)

        all_fork_projects = self.clowder.get_all_fork_project_names()
        if all_fork_projects == '':
            cprint(' - No forks to sync\n', 'red')
            sys.exit()
        self.clowder.sync(all_fork_projects,
                          rebase=self.args.rebase,
                          parallel=self.args.parallel)

    def version(self):
        """clowder version command"""

        print('clowder version ' + self._version + '\n')
        sys.exit()

    def yaml(self):
        """clowder yaml command"""

        if self.clowder_repo is None:
            exit_clowder_not_found()

        self.clowder_repo.print_status()
        if self._invalid_yaml:
            sys.exit(1)

        self.clowder.print_yaml(self.args.resolved)

    def _exit_handler_formatter(self):
        """Exit handler to display trailing newline"""

        if self._display_trailing_newline:
            print()

    def _validate_clowder_yaml(self):
        """Print invalid yaml message and exit if invalid"""

        if self._invalid_yaml:
            print(fmt.invalid_yaml_error())
            print(fmt.error(self._error))
            sys.exit(1)
コード例 #4
0
ファイル: cmd.py プロジェクト: JrGoodle/clowder
class Command(object):
    """Command class for parsing commandline options"""

    def __init__(self):
        self.root_directory = os.getcwd()
        self.clowder = None
        self.clowder_repo = None
        self.versions = None
        self.group_names = ''
        self.project_names = ''
        # Load current clowder.yml config if it exists
        clowder_path = os.path.join(self.root_directory, '.clowder')
        if os.path.isdir(clowder_path):
            clowder_symlink = os.path.join(self.root_directory, 'clowder.yaml')
            self.clowder_repo = ClowderRepo(self.root_directory)
            if not os.path.islink(clowder_symlink):
                print('')
                clowder_output = colored('.clowder', 'green')
                print(clowder_output)
                self.clowder_repo.link()
            self.clowder = ClowderController(self.root_directory)
            self.versions = self.clowder.get_saved_version_names()
            if self.clowder.get_all_group_names() is not None:
                self.group_names = self.clowder.get_all_group_names()
            if self.clowder.get_all_project_names() is not None:
                self.project_names = self.clowder.get_all_project_names()
        # clowder argparse setup
        command_description = 'Utility for managing multiple git repositories'
        parser = argparse.ArgumentParser(description=command_description)
        parser.add_argument('--version', '-v', action='store_true',
                            dest='clowder_version', help='Print clowder version')
        subparsers = parser.add_subparsers(dest='command')
        self._configure_subparser_clean(subparsers)
        self._configure_subparser_forall(subparsers)
        self._configure_subparser_herd(subparsers)
        self._configure_subparser_init(subparsers)
        self._configure_subparser_link(subparsers)
        self._configure_subparser_prune(subparsers)
        self._configure_subparser_repo(subparsers)
        self._configure_subparser_save(subparsers)
        self._configure_subparser_start(subparsers)
        self._configure_subparser_stash(subparsers)
        self._configure_subparser_status(subparsers)
        # Argcomplete and arguments parsing
        argcomplete.autocomplete(parser)
        self.args = parser.parse_args()

        if self.args.clowder_version:
            print('clowder version 1.1.2')
            sys.exit(0)
        print('')
        if self.args.command is None or not hasattr(self, self.args.command):
            exit_unrecognized_command(parser)
        # use dispatch pattern to invoke method with same name
        getattr(self, self.args.command)()
        print('')

    def clean(self):
        """clowder clean command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.projects is None:
                self.clowder.clean_groups(self.args.groups)
            else:
                self.clowder.clean_projects(self.args.projects)
        else:
            exit_clowder_not_found()

    def forall(self):
        """clowder forall command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.projects is None:
                if self.args.cmd is not None:
                    self.clowder.forall_groups_command(self.args.cmd[0], self.args.groups)
                else:
                    self.clowder.forall_groups_file(self.args.file[0], self.args.groups)
            else:
                if self.args.cmd is not None:
                    self.clowder.forall_projects_command(self.args.cmd[0], self.args.projects)
                else:
                    self.clowder.forall_projects_file(self.args.file[0], self.args.projects)
        else:
            exit_clowder_not_found()

    def herd(self):
        """clowder herd command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')

            if self.args.branch is None:
                ref = None
            else:
                ref = self.args.branch[0]

            if self.args.depth is None:
                depth = None
            else:
                depth = self.args.depth[0]

            if self.args.projects is None:
                if self.args.groups is None:
                    self.clowder.herd_groups(self.clowder.get_all_group_names(), ref, depth)
                else:
                    self.clowder.herd_groups(self.args.groups, ref, depth)
            else:
                self.clowder.herd_projects(self.args.projects, ref, depth)
        else:
            exit_clowder_not_found()

    def init(self):
        """clowder init command"""
        if self.clowder_repo is None:
            url_output = colored(self.args.url, 'green')
            print('Create clowder repo from ' + url_output)
            print('')
            clowder_repo = ClowderRepo(self.root_directory)
            clowder_repo.init(self.args.url, self.args.branch)
        else:
            cprint('Clowder already initialized in this directory', 'red')
            print('')
            sys.exit()

    def link(self):
        """clowder link command"""
        self.clowder_repo.print_status()

        if self.clowder_repo is not None:
            if self.args.version is None:
                version = None
            else:
                version = self.args.version[0]

            self.clowder_repo.link(version)
        else:
            exit_clowder_not_found()

    def prune(self):
        """clowder prune command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.projects is None:
                self.clowder.prune_groups(self.args.groups, self.args.branch, self.args.remote)
            else:
                self.clowder.prune_projects(self.args.projects, self.args.branch, self.args.remote)
        else:
            exit_clowder_not_found()

    def repo(self):
        """clowder repo command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            repo_command = 'repo_' + self.args.repo_command
            getattr(self, repo_command)()
        else:
            exit_clowder_not_found()

    def repo_add(self):
        """clowder repo add command"""
        if self.clowder_repo is not None:
            self.clowder_repo.add(self.args.files)
        else:
            exit_clowder_not_found()

    def repo_checkout(self):
        """clowder repo checkout command"""
        if self.clowder_repo is not None:
            self.clowder_repo.checkout(self.args.ref[0])
        else:
            exit_clowder_not_found()

    def repo_clean(self):
        """clowder repo clean command"""
        if self.clowder_repo is not None:
            self.clowder_repo.clean()
        else:
            exit_clowder_not_found()

    def repo_commit(self):
        """clowder repo commit command"""
        if self.clowder_repo is not None:
            self.clowder_repo.commit(self.args.message[0])
        else:
            exit_clowder_not_found()

    def repo_pull(self):
        """clowder repo pull command"""
        if self.clowder_repo is not None:
            self.clowder_repo.pull()
        else:
            exit_clowder_not_found()

    def repo_push(self):
        """clowder repo push command"""
        if self.clowder_repo is not None:
            self.clowder_repo.push()
        else:
            exit_clowder_not_found()

    def repo_run(self):
        """clowder repo run command"""
        if self.clowder_repo is not None:
            self.clowder_repo.run_command(self.args.cmd[0])
        else:
            exit_clowder_not_found()

    def repo_status(self):
        """clowder repo status command"""
        if self.clowder_repo is not None:
            self.clowder_repo.status()
        else:
            exit_clowder_not_found()

    def save(self):
        """clowder save command"""
        if self.clowder_repo is not None:
            self.clowder.save_version(self.args.version)
        else:
            exit_clowder_not_found()

    def start(self):
        """clowder start command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.projects is None:
                self.clowder.start_groups(self.args.groups, self.args.branch)
            else:
                self.clowder.start_projects(self.args.projects, self.args.branch)
        else:
            exit_clowder_not_found()

    def stash(self):
        """clowder stash command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.projects is None:
                self.clowder.stash_groups(self.args.groups)
            else:
                self.clowder.stash_projects(self.args.projects)
        else:
            exit_clowder_not_found()

    def status(self):
        """clowder status command"""
        if self.clowder_repo is not None:
            self.clowder_repo.print_status()
            print('')
            if self.args.fetch:
                print(' - Fetching upstream changes for projects', end="", flush=True)
                timer = RepeatedTimer(1, self._print_progress)
                if self.args.projects is None:
                    self.clowder.fetch_groups(self.args.groups)
                else:
                    self.clowder.fetch_projects(self.args.projects)
                timer.stop()
                print('\n')
            if self.args.projects is None:
                self.clowder.status_groups(self.args.groups, self.args.verbose)
            else:
                self.clowder.status_projects(self.args.projects, self.args.verbose)
        else:
            exit_clowder_not_found()

# Disable errors shown by pylint for too many local variables
# pylint: disable=R0201
    def _configure_subparser_clean(self, subparsers):
        """Configure clowder clean subparser and arguments"""
        # clowder clean
        clean_help = 'Discard current changes in all projects'
        parser_clean = subparsers.add_parser('clean', help=clean_help)
        group_clean = parser_clean.add_mutually_exclusive_group()
        group_clean.add_argument('--groups', '-g', choices=self.group_names,
                                 default=self.group_names, nargs='+',
                                 help='Groups to clean')
        group_clean.add_argument('--projects', '-p', choices=self.project_names,
                                 nargs='+', help='Projects to clean')

    def _configure_subparser_forall(self, subparsers):
        """Configure clowder forall subparser and arguments"""
        # clowder forall
        forall_help = 'Run command in project directories'
        parser_forall = subparsers.add_parser('forall', help=forall_help)
        group_forall_command = parser_forall.add_mutually_exclusive_group()
        group_forall_command.add_argument('--cmd', '-c', nargs=1,
                                          help='Command to run in project directories')
        group_forall_command.add_argument('--file', '-f', nargs=1, help='Script to run')
        group_forall_targets = parser_forall.add_mutually_exclusive_group()
        group_forall_targets.add_argument('--groups', '-g', choices=self.group_names,
                                          default=self.group_names, nargs='+',
                                          help='Groups to run command for')
        group_forall_targets.add_argument('--projects', '-p', choices=self.project_names,
                                          nargs='+', help='Projects to run command for')

    def _configure_subparser_herd(self, subparsers):
        """Configure clowder herd subparser and arguments"""
        # clowder herd
        herd_help = 'Clone and sync latest changes for projects'
        parser_herd = subparsers.add_parser('herd', help=herd_help)
        parser_herd.add_argument('--depth', '-d', default=None, type=int, nargs=1,
                                 help='Depth to herd')
        parser_herd.add_argument('--branch', '-b', nargs=1, default=None, help='Branch to herd')
        group_herd = parser_herd.add_mutually_exclusive_group()
        group_herd.add_argument('--groups', '-g', choices=self.group_names,
                                default=self.group_names, nargs='+', help='Groups to herd')
        group_herd.add_argument('--projects', '-p', choices=self.project_names,
                                nargs='+', help='Projects to herd')

    def _configure_subparser_init(self, subparsers):
        """Configure clowder init subparser and arguments"""
        # clowder init
        init_help = 'Clone repository to clowder directory and create clowder.yaml symlink'
        parser_init = subparsers.add_parser('init', help=init_help)
        parser_init.add_argument('url', help='URL of repo containing clowder.yaml')
        parser_init.add_argument('--branch', '-b', default='master', nargs='?',
                                 help='Branch of repo containing clowder.yaml')

    def _configure_subparser_link(self, subparsers):
        """Configure clowder link subparser and arguments"""
        # clowder link
        parser_link = subparsers.add_parser('link', help='Symlink clowder.yaml version')
        parser_link.add_argument('--version', '-v', choices=self.versions, nargs=1,
                                 default=None, help='Version name to symlink')

    def _configure_subparser_prune(self, subparsers):
        """Configure clowder prune subparser and arguments"""
        # clowder prune
        parser_prune = subparsers.add_parser('prune', help='Prune old branch')
        parser_prune.add_argument('branch', help='Name of branch to remove')
        parser_prune.add_argument('--remote', '-r', action='store_true',
                                  help='Prune remote branches')
        group_prune = parser_prune.add_mutually_exclusive_group()
        group_prune.add_argument('--groups', '-g', choices=self.group_names,
                                 default=self.group_names, nargs='+',
                                 help='Groups to prune branch for')
        group_prune.add_argument('--projects', '-p', choices=self.project_names,
                                 nargs='+', help='Projects to prune branch for')

    def _configure_subparser_repo(self, subparsers):
        """Configure clowder repo subparser and arguments"""
        # clowder repo
        parser_repo = subparsers.add_parser('repo', help='Manage clowder repo')
        repo_subparsers = parser_repo.add_subparsers(dest='repo_command')
        # clowder repo add
        repo_add_help = 'Add files in clowder repo'
        parser_repo_add = repo_subparsers.add_parser('add', help=repo_add_help)
        parser_repo_add.add_argument('files', nargs='+', help='Files to add')
        # clowder repo checkout
        repo_checkout_help = 'Checkout ref in clowder repo'
        parser_repo_checkout = repo_subparsers.add_parser('checkout', help=repo_checkout_help)
        parser_repo_checkout.add_argument('ref', nargs=1, help='Git ref to checkout')
        # clowder repo clean
        repo_clean_help = 'Discard changes in clowder repo'
        repo_subparsers.add_parser('clean', help=repo_clean_help)
        # clowder repo commit
        repo_commit_help = 'Commit current changes in clowder repo yaml files'
        parser_repo_commit = repo_subparsers.add_parser('commit', help=repo_commit_help)
        parser_repo_commit.add_argument('message', nargs=1, help='Commit message')
        # clowder repo run
        repo_run_help = 'Run command in clowder repo'
        parser_repo_run = repo_subparsers.add_parser('run', help=repo_run_help)
        repo_run_command_help = 'Command to run in clowder repo directory'
        parser_repo_run.add_argument('cmd', nargs=1, help=repo_run_command_help)
        # clowder repo pull
        repo_pull_help = 'Pull upstream changes in clowder repo'
        repo_subparsers.add_parser('pull', help=repo_pull_help)
        # clowder repo push
        repo_subparsers.add_parser('push', help='Push changes in clowder repo')
        # clowder repo status
        repo_subparsers.add_parser('status', help='Print clowder repo git status')

    def _configure_subparser_save(self, subparsers):
        """Configure clowder save subparser and arguments"""
        # clowder save
        save_help = 'Create version of clowder.yaml for current repos'
        parser_save = subparsers.add_parser('save', help=save_help)
        parser_save.add_argument('version', help='Version name to save')

    def _configure_subparser_start(self, subparsers):
        """Configure clowder start subparser and arguments"""
        # clowder start
        parser_start = subparsers.add_parser('start', help='Start a new feature')
        parser_start.add_argument('branch', help='Name of branch to create')
        group_start = parser_start.add_mutually_exclusive_group()
        group_start.add_argument('--groups', '-g', choices=self.group_names,
                                 default=self.group_names, nargs='+',
                                 help='Groups to start feature for')
        group_start.add_argument('--projects', '-p', choices=self.project_names,
                                 nargs='+', help='Projects to start feature for')

    def _configure_subparser_stash(self, subparsers):
        """Configure clowder stash subparser and arguments"""
        # clowder stash
        parser_stash = subparsers.add_parser('stash',
                                             help='Stash current changes')
        group_stash = parser_stash.add_mutually_exclusive_group()
        group_stash.add_argument('--groups', '-g', choices=self.group_names,
                                 default=self.group_names, nargs='+',
                                 help='Groups to stash')
        group_stash.add_argument('--projects', '-p', choices=self.project_names,
                                 nargs='+', help='Projects to stash')

    def _configure_subparser_status(self, subparsers):
        """Configure clowder status subparser and arguments"""
        # clowder status
        parser_status = subparsers.add_parser('status', help='Print project status')
        parser_status.add_argument('--fetch', '-f', action='store_true',
                                   help='Fetch projects before printing status')
        parser_status.add_argument('--verbose', '-v', action='store_true',
                                   help='Print detailed diff status')
        group_status = parser_status.add_mutually_exclusive_group()
        group_status.add_argument('--groups', '-g', choices=self.group_names,
                                  default=self.group_names, nargs='+',
                                  help='Groups to print status for')
        group_status.add_argument('--projects', '-p', choices=self.project_names,
                                  nargs='+', help='Projects to print status for')


    def _print_progress(self):
        print('.', end="", flush=True)