Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ Note, "non-notable" changes may be small patches with no noticeable effect to th
## Unreleased


## 0.2.0 2020-12-17
### Added
- Configuration file
- `~/.jockrc` stores repositories and groups for commands to be run on (YAML formatted)
- `--group` can be used to refer to groups of repositories (same usage as repositories)

### Changed
- `--respository` now refers to repository stored in `.jockrc`, previously looked at adjacent directories


## 0.1.0 2020-11-27
### Added
- Initial usage: `jock [OPTIONS] COMMAND [ARGS]`
Expand Down
73 changes: 36 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,47 @@ execution._

## Usage

### Configuration

Repositories and groups must be configured in `~/.jockrc`, in YAML format like below

```yaml
repositories:
auth-service:
address: git@github.com:some-startup/authentication-service.git
location: /home/jock/git/authentication-service
shared-entities:
address: git@github.com:some-startup/shared-entities.git
location: ~/shared-entities
...
user-service:
address: git@github.com:some-startup/user-service.git
location: ../users

groups:
- name: services
repositories:
- auth-service
- user-service
```

- `address` is the remote git address
- `location` is the local location, can be relative to home or absolute

### CLI Usage

```
Usage: jock [OPTIONS] COMMAND [ARGS]...

Options:
--version Show the version and exit.
-r, --repository TEXT Repository you wish to run commands on. Multiple
repositories can be specified using multiple flags.
-r, --repository TEXT Repository, specified in ~/.jockrc, you wish to run
commands on. Multiple repositories can be specified
using multiple flags.

-g, --group TEXT Group of repositories, specified in ~/.jockrc, you
wish to run commands on.Multiple groups can be
specified using multiple flags.

--help Show this message and exit.

Expand All @@ -82,46 +116,11 @@ some-service`
`pull`, `push`, `reset`, `restore`, `rm`, `switch`, or `tag`
- ARGS are git arguments passed directly to the git command

Until grouping is supported (see the roadmap below), you can save your repo groups using environment variables in your
.bashrc file, e.g:
```shell script
export SERVICES="--repository auth-service --repository user-service"
```
Then you can run your commands as `jock $(echo $SERVICES) checkout main`


## Roadmap

This is a loose roadmap to explain where the tool will end up, the versions & functionality against them are open to
changes.

### 0.2

Stored repository settings and groups.

e.g. with a config of
```yaml
repositories:
- name: auth-service
address: git@github.com:some-startup/authentication-service.git
directory: ../authentication-service
- name: shared-entities
address: git@github.com:some-startup/shared-entities.git
directory: ../shared-entities
- ...
- name: user-service
address: git@github.com:some-startup/user-service.git
directory: ../users

groups:
- name: services
repositories:
- auth-service
- user-service
```
Commands could be grouped without stating individual repositories

`jock -g=services checkout -b update-shared-entities-version`

### 0.3 +

Expand Down
42 changes: 25 additions & 17 deletions jock/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import click

from jock import __version__
from jock.config import load_repositories
from jock.config import get_selected_repositories
from jock.git import git_command

CONFIG_REPOSITORIES = 'config_repositories'
Expand All @@ -17,102 +17,110 @@
'you wish to run commands on. Multiple '
'repositories can be specified using '
'multiple flags.')
@click.option('--group', '-g', type=str, multiple=True,
help='Group of repositories, specified in '
'~/.jockrc, you wish to run commands on.'
'Multiple groups can be specified using '
'multiple flags.')
@click.pass_context
def main(ctx, repository):
def main(ctx, repository, group):
ctx.ensure_object(dict)
ctx.obj[CONFIG_REPOSITORIES] = load_repositories()
ctx.obj[SELECTED_REPOSITORIES] = tuple(map(lambda x: x.lstrip(" ="), repository))

repositories = tuple(map(lambda x: x.lstrip(" ="), repository))
groups = tuple(map(lambda x: x.lstrip(" ="), group))

ctx.obj[SELECTED_REPOSITORIES] = get_selected_repositories(repositories, groups)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def clone(ctx, git_args):
git_command('clone', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('clone', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def add(ctx, git_args):
git_command('add', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('add', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def restore(ctx, git_args):
git_command('restore', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('restore', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def rm(ctx, git_args):
git_command('rm', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('rm', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def branch(ctx, git_args):
git_command('branch', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('branch', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def commit(ctx, git_args):
git_command('commit', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('commit', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def reset(ctx, git_args):
git_command('reset', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('reset', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def switch(ctx, git_args):
git_command('switch', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('switch', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def tag(ctx, git_args):
git_command('tag', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('tag', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def fetch(ctx, git_args):
git_command('fetch', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('fetch', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def pull(ctx, git_args):
git_command('pull', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('pull', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def push(ctx, git_args):
git_command('push', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('push', ctx.obj[SELECTED_REPOSITORIES], git_args)


@main.command(context_settings=CONTEXT_SETTINGS)
@click.pass_context
@click.argument(GIT_ARGS, nargs=-1, required=False)
def checkout(ctx, git_args):
git_command('checkout', ctx.obj[CONFIG_REPOSITORIES], ctx.obj[SELECTED_REPOSITORIES], git_args)
git_command('checkout', ctx.obj[SELECTED_REPOSITORIES], git_args)


if __name__ == '__main__':
Expand Down
22 changes: 19 additions & 3 deletions jock/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@
from yaml import Loader, Dumper


def load_repositories():
def load_config():
with open(os.path.expanduser('~/.jockrc'), 'r') as file:
config = yaml.load(file, Loader=Loader)
return config['repositories']
return yaml.load(file, Loader=Loader)


def get_selected_repositories(selected_repositories, selected_groups):
config = load_config()

repositories = dict({})
config_repositories = config['repositories']
for repo_name in selected_repositories:
repositories[repo_name] = config_repositories[repo_name]

if config.get('groups') is not None:
config_groups = config['groups']
for group_name in selected_groups:
for repo_name in config_groups[group_name]['repositories']:
repositories[repo_name] = config_repositories[repo_name]

return repositories
23 changes: 12 additions & 11 deletions jock/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
from jock.utils import get_repository_path


def git_common(command, config_repositories, selected_repositories, git_args=()):
def git_common(command, selected_repositories, git_args=()):
for repository_name in selected_repositories:
repository_path = get_repository_path(config_repositories[repository_name]['location'])
repository = selected_repositories[repository_name]
repository_path = get_repository_path(repository['location'])
click.echo('Executing [{}] in [{}]'.format(command, repository_path))
subprocess.run(('git', '-C', repository_path, command) + git_args)


def git_clone(config_repositories, selected_repositories, git_args=()):
def git_clone(selected_repositories, git_args=()):
for repository_name in selected_repositories:
config_repository = config_repositories[repository_name]
repository_path = get_repository_path(config_repository['location'])
repository = selected_repositories[repository_name]
repository_path = get_repository_path(repository['location'])
click.echo(
'Cloning [{}] in [{}]'.format(repository_name, repository_path)
)
subprocess.run(('git', 'clone', config_repository['address'], repository_path) + git_args)
subprocess.run(('git', 'clone', repository['address'], repository_path) + git_args)


GIT_COMMANDS = {
'clone': lambda _, cr, sr, a: git_clone(cr, sr, a),
'clone': lambda _, sr, a: git_clone(sr, a),
'add': git_common,
'restore': git_common,
'rm': git_common,
Expand All @@ -40,11 +41,11 @@ def git_clone(config_repositories, selected_repositories, git_args=()):
}


def git_command(command, config_repositories, selected_repositories, git_args):
release_func = GIT_COMMANDS.get(command)
def git_command(command, selected_repositories, git_args):
git_func = GIT_COMMANDS.get(command)

if release_func is None:
if git_func is None:
print('Unsupported command ' + command)
sys.exit(1)

release_func(command, config_repositories, selected_repositories, git_args)
git_func(command, selected_repositories, git_args)
40 changes: 20 additions & 20 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class TestCLI(TestCase):
def setUp(self):
self.runner = CliRunner()

@patch('jock.cli.load_repositories')
@patch('jock.cli.get_selected_repositories')
@patch('jock.cli.git_command')
def test_git_commands(self, git_command, load_repositories_mock):
def test_git_commands(self, git_command, get_selected_repositories_mock):
# Given
load_repositories_mock.return_value = CONFIG_REPOSITORIES
get_selected_repositories_mock.return_value = CONFIG_REPOSITORIES
commands = [
'clone',
'add',
Expand All @@ -35,19 +35,19 @@ def test_git_commands(self, git_command, load_repositories_mock):

args = ('-a', '--woof')
expected_calls = [
call(commands[0], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[1], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[2], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[3], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[4], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[5], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[6], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[7], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[8], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[9], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[10], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[11], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[12], CONFIG_REPOSITORIES, REPOSITORY_NAMES, args),
call(commands[0], CONFIG_REPOSITORIES, args),
call(commands[1], CONFIG_REPOSITORIES, args),
call(commands[2], CONFIG_REPOSITORIES, args),
call(commands[3], CONFIG_REPOSITORIES, args),
call(commands[4], CONFIG_REPOSITORIES, args),
call(commands[5], CONFIG_REPOSITORIES, args),
call(commands[6], CONFIG_REPOSITORIES, args),
call(commands[7], CONFIG_REPOSITORIES, args),
call(commands[8], CONFIG_REPOSITORIES, args),
call(commands[9], CONFIG_REPOSITORIES, args),
call(commands[10], CONFIG_REPOSITORIES, args),
call(commands[11], CONFIG_REPOSITORIES, args),
call(commands[12], CONFIG_REPOSITORIES, args),
]
flagged_repositories = \
map_list_with_repository_flag(REPOSITORY_NAMES)
Expand All @@ -59,11 +59,11 @@ def test_git_commands(self, git_command, load_repositories_mock):
git_command.assert_has_calls(expected_calls)
self.assertEqual(git_command.call_count, len(expected_calls))

@patch('jock.cli.load_repositories')
@patch('jock.cli.get_selected_repositories')
@patch('jock.cli.git_command')
def test_all_repository_flags_work(self, git_mock, load_repositories_mock):
def test_all_repository_flags_work(self, git_mock, get_selected_repositories_mock):
# Given
load_repositories_mock.return_value = CONFIG_REPOSITORIES
get_selected_repositories_mock.return_value = CONFIG_REPOSITORIES
flagged_repository_names = (
'--repository=' + REPOSITORY_NAMES[0],
'-r ' + REPOSITORY_NAMES[1],
Expand All @@ -72,4 +72,4 @@ def test_all_repository_flags_work(self, git_mock, load_repositories_mock):
# When
self.runner.invoke(main, flagged_repository_names + ('clone',))
# Then
git_mock.assert_called_once_with('clone', CONFIG_REPOSITORIES, REPOSITORY_NAMES, ())
git_mock.assert_called_once_with('clone', CONFIG_REPOSITORIES, ())
Loading