Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from kylef/kylef/repo
Add `repo` subcommand to operate on collection of repos
- Loading branch information
Showing
4 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
Repo | ||
==== | ||
|
||
Commands to operate on a collection of repositories at a time. | ||
|
||
----- | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |