Skip to content

Commit

Permalink
Cleanup usage documentation and make options a little more consistent
Browse files Browse the repository at this point in the history
  • Loading branch information
borntyping committed Mar 22, 2022
1 parent 64e70c0 commit 0f03283
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 123 deletions.
71 changes: 45 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
git river
=========

`git river workspace` will manage a "workspace" path you configure, cloning
and managing repositories from configured GitHub and GitLab groups.
`git-river` is a tool designed to make it easier to work with large
numbers of GitHub and GitLab projects and "forking" workflow that involve
pulling changes from "upstream" repositories and pushing to "downstream"
repositories.

Repositories will be organized by the domain and path of the remote GitHub
repository or GitLab project.
`git-river` will manage a "workspace" path you configure, cloning repositories
into that directory with a tree-style structure organised by domain, project
namespace, and project name.

```
$ tree ~/workspace
~/workspace
├── github.com
│ └── datto
│ └── example
│ └── git-river
└── gitlab.com
└── datto
└── example
└── git-river
```

Links
Expand All @@ -39,37 +42,53 @@ Usage
Run `git-river <subcommand>`. Git's builtin aliasing also allows you to
run `git river` instead.

```bash
git-river --help
```
Before you can use `git-river` you must configure a workspace path by running
`git-river init PATH` or setting the `GIT_RIVER_WORKSPACE` environment variable.
This should point to a directory `git-river` can use to clone git repositories
into.

Several commands will attempt to discover various names, and usually have an
option flag to override discovery.

- The "upstream" remote is the first of `upstream` or `origin` that exists. Override with `--upstream`.
- The "downstream" remote is the first of `downstream` that exists. Override with `--downstream`.
- The "mainline" branch is the first of `main` or `master` that exists. Override with `--mainline`.

### Subcommands

- `git river config` displays the current configuration.
- `git river clone URL...` clones a repository into the workspace path.

- `git river workspace` manages the workspace path.
- `git river config` manages the configuration file.

Run without any subcommand, it runs all workspace subcommands except `list`
and `fetch`.
- `git river config display` prints the loaded configuration as JSON. Credentials are redacted.
- `git river config init` creates an initial config file.
- `git river config workspace` prints the workspace path.

- `git river workspace clone` clones repositories.
- `git river workspace configure` sets git config options.
- `git river workspace fetch` fetches each git remote.
- `git river workspace list` displays remote repos that will be cloned.
- `git river workspace remotes` sets `upstream` and `origin` remotes.
- `git river workspace tidy` deletes merged branches.
- `git river forge` manages repositories listed by GitHub and GitLab.

- `git river repo` manages the repository in the current directory.
- `git river forge` runs the `clone` + `archived` + `configure` + `remotes` subcommands.
- `git river forge clone` clones repositories.
- `git river forge configure` sets git config options.
- `git river forge fetch` fetches each git remote.
- `git river forge list` displays remote repositories that will be cloned.
- `git river forge remotes` sets `upstream`+`downstream` or `origin` remotes.
- `git river forge tidy` deletes branches merged into the mainline branch.
- `git river forge archived` lists archived repositories that exist locally.

This mostly matches the features from the `workspace` subcommand.
- `git river` also provides some "loose" subcommands that work on the repository
in the current directory, mostly matching the features from the `forge`
subcommand.

- `git river repo configure` sets git config options.
- `git river repo fetch` fetches each git remote.
- `git river repo remotes` sets `upstream` and `origin` remotes.
- `git river repo tidy` deletes merged branches.
- `git river fetch` fetches all git remotes.
- `git river merge` creates the merge result of all `feature/*` branches.
- `git river tidy` deletes branches merged into the mainline branch.
- `git river restart` rebases the current branch from the upstream remotes mainline branch.

Configuration
-------------

Configuration is a JSON object read from `~/.config/git-river/config.json`.
Configuration is a JSON object read from `~/.config/git-river/config.json`. Run
`git-river config init` to create an example configuration file.

- `path` - path to a directory to use as the "workspace".
- `forges` - a map of forges.
Expand Down
6 changes: 1 addition & 5 deletions git_river/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ def main(ctx: click.Context) -> None:


main.add_command(git_river.commands.clone.main)
main.add_command(git_river.commands.config.display_config)
main.add_command(git_river.commands.config.display_workspace)
main.add_command(git_river.commands.config.init_config)
main.add_command(git_river.commands.config.main)
main.add_command(git_river.commands.forge.main)
main.add_command(git_river.commands.repo.configure_options)
main.add_command(git_river.commands.repo.configure_remotes)
main.add_command(git_river.commands.repo.fetch_remotes)
main.add_command(git_river.commands.repo.merge_feature_branches)
main.add_command(git_river.commands.repo.tidy_branches)
Expand Down
10 changes: 5 additions & 5 deletions git_river/commands/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@

@click.command(name="clone")
@click.argument(
"repositories",
metavar="REPOSITORY",
"urls",
metavar="URL...",
type=click.STRING,
nargs=-1,
)
@click.pass_obj
def main(config: git_river.config.Config, repositories: typing.Sequence[str]) -> None:
"""Clone a repository to the workspace path."""
for url in repositories:
def main(config: git_river.config.Config, urls: typing.Sequence[str]) -> None:
"""Clone repositories to the workspace path."""
for url in urls:
config.repository_from_url(url).clone(verbose=True)
14 changes: 11 additions & 3 deletions git_river/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@
import git_river.config


@click.command(name="config")
@click.group(name="config")
def main():
"""Manage git-river's own configuration file."""
pass


@main.command(name="display")
@click.pass_obj
def display_config(config: git_river.config.Config) -> None:
"""Dump the current configuration as JSON."""
print(config.json(indent=2, by_alias=True))


@click.command(name="init")
@main.command(name="init")
@click.argument(
"workspace",
type=click.Path(
Expand All @@ -46,6 +52,8 @@ def display_config(config: git_river.config.Config) -> None:
)
@click.pass_obj
def init_config(config: git_river.config.Config, workspace: str) -> None:
"""Create the configuration file."""

config.workspace = pathlib.Path(workspace)

if git_river.config.CONFIG_PATH.exists():
Expand All @@ -57,7 +65,7 @@ def init_config(config: git_river.config.Config, workspace: str) -> None:
git_river.config.CONFIG_PATH.write_text(config.json(indent=2, by_alias=True))


@click.command(name="workspace")
@main.command(name="workspace")
@click.pass_obj
def display_workspace(config: git_river.config.Config) -> None:
"""Print the workspace path."""
Expand Down
37 changes: 22 additions & 15 deletions git_river/commands/forge.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,18 @@ def value_or_default(value: typing.Optional[T], default: T) -> T:
"--forge",
"select_forges",
default=(),
metavar="NAME",
multiple=True,
help="Use repositories from specific forges.",
)
@click.option(
"-g",
"-o",
"--group",
"--organization",
"--org",
"select_groups",
default=(),
metavar="NAME",
multiple=True,
help="Use repositories from specific groups.",
)
Expand All @@ -87,6 +89,7 @@ def value_or_default(value: typing.Optional[T], default: T) -> T:
"--user",
"select_users",
default=(),
metavar="NAME",
multiple=True,
help="Use repositories from specific users.",
)
Expand All @@ -95,8 +98,9 @@ def value_or_default(value: typing.Optional[T], default: T) -> T:
"--self",
"select_self",
default=None,
metavar="NAME",
is_flag=True,
help="Use repositories from specific users.",
help="Use repositories from the authenticated user.",
)
@click.pass_context
def main(
Expand All @@ -109,11 +113,11 @@ def main(
"""
Clone and manage repositories from GitLab and GitHub in bulk.
Selects from all forges unless '--forge' flags are passed.
Selects from all repositories unless any '--group', '--user', or '--self' flags are passed.
Selects from all forges unless '--forge' flags are passed. Selects from all repositories unless
any '--group', '--user', or '--self' flags are passed.
Invokes the clone, configure, and remotes subcommands when no subcommand is given.
Invokes the 'clone', 'archived', 'configure', and 'remotes' subcommands when no subcommand is
given.
"""
config = ctx.ensure_object(git_river.config.Config)
ctx.obj = RepositoryManager()
Expand Down Expand Up @@ -146,14 +150,6 @@ def main(
ctx.invoke(configure_remotes)


@main.command(name="archived")
@click.pass_obj
def archived_repositories(workspace: RepositoryManager) -> None:
for repo in workspace.existing():
if repo.archived:
logger.warning("Local repository is archived", repo=repo.name)


@main.command(name="clone")
@click.pass_obj
def clone_repositories(workspace: RepositoryManager) -> None:
Expand Down Expand Up @@ -182,6 +178,15 @@ def clone_repositories(workspace: RepositoryManager) -> None:
local_repo.bind(logger).info("Cloned repository")


@main.command(name="archived")
@click.pass_obj
def archived_repositories(workspace: RepositoryManager) -> None:
"""Display a warning for archived repositories that exist locally."""
for repo in workspace.existing():
if repo.archived:
logger.warning("Local repository is archived", repo=repo.name)


@main.command(name="configure")
@click.pass_obj
def configure_options(workspace: RepositoryManager) -> None:
Expand Down Expand Up @@ -243,7 +248,9 @@ def tidy(workspace: RepositoryManager, dry_run: bool, target: typing.Optional[st
"""
logger.info("Removing merged branches")
for repo in workspace.existing():
repo.remove_merged_branches(target=repo.target_or_mainline_branch(target), dry_run=dry_run)
mainline = repo.discover_mainline_branch(target)

repo.remove_merged_branches(target=mainline, dry_run=dry_run)


@main.command(name="list")
Expand Down
Loading

0 comments on commit 0f03283

Please sign in to comment.