Skip to content

Commit

Permalink
Merge pull request #22 from kylef/kylef/repo
Browse files Browse the repository at this point in the history
Add `repo` subcommand to operate on collection of repos
  • Loading branch information
kylef committed Aug 30, 2018
2 parents 4c7c4f6 + 2867879 commit ac8685e
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Expand Up @@ -57,6 +57,7 @@ Maintain supports the following releasers:
.. toctree::
:maxdepth: 1

repo
release/hooks
release/git
release/github
Expand Down
44 changes: 44 additions & 0 deletions docs/repo.rst
@@ -0,0 +1,44 @@
Repo
====

Commands to operate on a collection of repositories at a time.

print
-----

Subcommand to print all matched repositories, this is useful to test which repositories will be used for other operations.

check
-----

Checks all repositories for unstaged, unsynced or untracked changes.

.. code-block::
$ maintain repo check
goji
- Branch is not master
- Repository has unstaged changes
run
---

Allows running the provided command on a collection of repositories.

.. code-block::
$ maintain repo run 'npm install && npm test'
Options

-s/--silent - Don't print subcommand output
--exit - Exit after first failure (non zero exit)

cp
--

Copies a file into each repository.

.. code-block::
$ maintain repo cp contributing.md .github/CONTRIBUTING.md
2 changes: 2 additions & 0 deletions maintain/commands/__init__.py
@@ -1,6 +1,7 @@
import click

from maintain.commands.release import release
from maintain.commands.repo import repo


@click.group()
Expand All @@ -9,3 +10,4 @@ def cli():


cli.add_command(release)
cli.add_command(repo)
124 changes: 124 additions & 0 deletions maintain/commands/repo.py
@@ -0,0 +1,124 @@
import os
import sys
import subprocess
from shutil import copyfile

import click
from git import Repo

from maintain.process import chdir


def gather_repositories():
"""
Collects all of the repositories. The current implementation
searches for them in the current working directory.
"""

for (root, dirs, files) in os.walk('.', topdown=True):
if '.git' not in dirs:
continue

for dir in list(dirs):
dirs.remove(dir)

path = os.path.split(root)[1]
repo = os.path.basename(path)
yield (repo, root)


@click.group()
def repo():
pass


@repo.command('print')
def print_command():
"""
Prints all repos.
"""

for (repo, path) in gather_repositories():
print(repo)


@repo.command()
@click.argument('command', nargs=-1)
@click.option('--exit/--no-exit', default=False)
@click.option('--silent/--no-silent', '-s', default=False)
def run(command, exit, silent):
"""
Runs given command on all repos and checks status
$ maintain repo run -- git checkout master
"""

status = 0

for (repo, path) in gather_repositories():
with chdir(path):
result = subprocess.run(command, shell=True, capture_output=silent)
if result.returncode != 0:
status = result.returncode

print('Command failed: {}'.format(repo))

if exit:
break

sys.exit(status)


@repo.command()
@click.option('--exit/--no-exit', default=False)
def check(exit):
status = 0

for (name, path) in gather_repositories():
with chdir(path):
repo = Repo()
failures = []

if repo.head.ref != repo.heads.master:
failures.append('Branch is not master')

if repo.is_dirty():
failures.append('Repository has unstaged changes')

if len(repo.untracked_files) > 0:
failures.append('Repository has untracked files')

if repo.remotes.origin.refs.master.commit != repo.head.ref.commit:
failures.append('Branch has unsynced changes')

if len(failures) > 0:
status = 1
print(name)

for failure in failures:
print(' - {}'.format(failure))

if exit:
break

sys.exit(status)


@repo.command()
@click.argument('src', nargs=-1, type=click.Path(exists=True))
@click.argument('dst', nargs=1, type=click.Path())
def cp(src, dst):
status = 0

for (repo, path) in gather_repositories():
for filename in src:
destination = os.path.join(path, dst)

if os.path.exists(destination):
status = 1
print('Cannot copy to {}, {} exists'.format(repo, dst))
continue

copyfile(filename, destination)

sys.exit(status)

0 comments on commit ac8685e

Please sign in to comment.