Metadata-Version: 2.5
Name: repoclone-cli
Version: 0.1.0
Summary: Bulk clone, update and report on every repository in a GitLab group or GitHub organisation
Project-URL: Homepage, https://github.com/devops-monk/repoclone-cli
Project-URL: Issues, https://github.com/devops-monk/repoclone-cli/issues
License: MIT
License-File: LICENSE
Keywords: cli,clone,devtools,git,github,gitlab
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: pygithub>=2.3
Requires-Dist: python-gitlab>=4.4
Requires-Dist: rich>=13.7
Description-Content-Type: text/markdown

# repoclone

Clone, update and report on **every repository in a GitLab group or GitHub organisation** — in parallel, safely, and repeatably.

Point it at a namespace and it mirrors the whole tree onto your laptop. Run it again next week and it fast-forwards what changed, leaves your local work alone, and tells you what happened.

```console
$ repoclone clone platform/tooling --token glpat-xxxx
gitlab platform/tooling → /Users/you/src/work
   cloned  platform/tooling/api
   cloned  platform/tooling/cli
  updated  platform/tooling/docs
  skipped  platform/tooling/legacy (local changes, left alone)

42 repositories: 30 cloned, 9 updated, 2 unchanged, 1 skipped
```

## Install

Requires Python 3.11+ and `git` on your PATH.

```bash
# recommended — isolated install, command on your PATH
uv tool install repoclone-cli

# or with pipx
pipx install repoclone-cli

# or plain pip
pip install repoclone-cli

# or straight from the repository, before a release is cut
uv tool install git+https://github.com/devops-monk/repoclone-cli
```

The package is published as **`repoclone-cli`** and installs a command called **`repoclone`** — the
name `repoclone` was already taken on PyPI by an unrelated project.

Check everything is wired up:

```bash
repoclone check --token glpat-xxxx
```

New to it? Follow [the step-by-step first test](docs/usage.md#step-by-step-first-test) — the first four steps cannot touch your disk.

## Quick start

```bash
# GitLab group (path or numeric id), token on the command line
repoclone clone platform/tooling --token glpat-xxxx --dest ~/src/work

# GitHub organisation
repoclone clone kubernetes --provider github --token ghp_xxxx --dest ~/src/oss

# see what would happen first
repoclone clone platform/tooling --token glpat-xxxx --dry-run
```

**Run the same command again tomorrow.** Existing clones are pulled up to date, anything new is cloned, and repositories with uncommitted work are left alone. That is the default; `--sync` says it explicitly if you prefer it spelled out in a script:

```bash
repoclone clone platform/tooling --token glpat-xxxx --sync
```

### Private / self-hosted instances

Pass your own domain with `--host` — this is the common case for company GitLab and GitHub Enterprise, and everything else works identically:

```bash
# self-hosted GitLab on your own domain
repoclone clone platform/tooling \
  --host https://git.tech.example.com \
  --token glpat-xxxx \
  --dest ~/src/work

# GitHub Enterprise Server
repoclone clone platform \
  --provider github \
  --host https://github.example.com \
  --token ghp_xxxx \
  --dest ~/src/work

# internal host with a self-signed certificate
repoclone clone platform/tooling --host https://git.internal.example.com --token $T --insecure
```

The API path is derived for you (`/api/v4` for GitLab, `/api/v3` for GitHub Enterprise) — give it the plain domain. Without `--host` the public gitlab.com / github.com is used. Put the host in a profile once and you never type it again.

Re-running is the normal case: repositories already on disk are fast-forwarded, new ones are cloned, and anything you have edited locally is left untouched.

## Commands

| Command | What it does |
|---|---|
| `repoclone clone [NAMESPACE]...` | Clone new repositories, update existing ones |
| `repoclone list [NAMESPACE]...` | List what would be cloned — no disk changes |
| `repoclone report` | Branch, last commit, dirty state of clones already on disk |
| `repoclone check` | Verify git, config and token before a large run |
| `repoclone config init` | Write a starter configuration file |
| `repoclone config show` | Show the settings that would apply, and from where |

Every command supports `-h/--help`. Passing an unknown option or a bad value prints the full help for that command, so you never have to guess.

## Common options

| Option | Default | Notes |
|---|---|---|
| `--token TEXT` | — | Read-only token. `read_api` (GitLab) or `repo:read` (GitHub) |
| `--provider [gitlab\|github]` | `gitlab` | Which forge to talk to |
| `--host URL` | public forge | Self-hosted GitLab or GitHub Enterprise |
| `--dest, -d PATH` | current directory | Where the clones go |
| `--protocol [ssh\|https]` | `ssh` | `https` uses the token for auth |
| `--sync` | on | Pull existing clones, clone new ones. Explicit form of the default |
| `--update, -u [pull\|fetch\|skip]` | `pull` | What to do with existing clones |
| `--jobs, -j N` | `8` | Parallel git operations |
| `--include / --exclude REGEX` | — | Filter by repository path. Repeatable |
| `--depth N` | full | Shallow clone |
| `--blobless` | off | Partial clone: full history, file contents on demand |
| `--flat / --nested` | nested | Flatten `a/b/c` into one directory level |
| `--archived / --forks` | excluded | Include archived repositories or forks |
| `--timeout N` | `30` | Seconds allowed per git operation. Raise it for large repositories |
| `--dry-run` | off | Show the plan, change nothing |
| `--prune` | off | Report local clones that no longer exist remotely |
| `--insecure` | off | Skip TLS verification (self-signed internal forge) |

### Useful combinations

```bash
# a fast, space-efficient mirror for code search
repoclone clone platform --token $T --blobless -j 16

# only the services, skipping anything archived-looking
repoclone clone platform --token $T --include '/services/' --exclude '^platform/old-'

# refresh remote refs without ever touching working trees
repoclone clone platform --token $T --update fetch

# what have I got locally that is going stale?
repoclone report --stale-days 180 --output table

# daily refresh: pull everything, clone anything new
repoclone clone platform --token $T --sync

# include forks and archived repositories in a full audit
repoclone clone platform --token $T --forks --archived

# fail fast on a stuck repository instead of waiting ten minutes
repoclone clone platform --token $T --timeout 120
```

## Tokens

Pass the token on the command line with `--token`, which is what most people do:

```bash
repoclone clone platform/tooling --token glpat-xxxx
```

If you would rather not have it in shell history, any of these also work — in this order of precedence:

1. `--token`
2. `REPOCLONE_TOKEN`
3. `token_env` in your profile (points at a variable of your choosing)
4. `GITLAB_TOKEN` / `GITHUB_TOKEN`
5. `token_cmd` in your profile, e.g. `op read op://Private/GitLab/token`

A read-only scope is enough. Over `https`, the token is used for the clone and then **stripped from the saved remote**, so it never lands in `.git/config`. Tokens are redacted from error output.

## Configuration profiles

Typing the same flags every day gets old. `repoclone config init` writes `~/.config/repoclone/config.toml`:

```toml
default_profile = "work"

[profiles.work]
provider = "gitlab"
host = "https://gitlab.example.com"
namespaces = ["platform/tooling"]
dest = "~/src/work"
protocol = "ssh"
concurrency = 8
token_env = "GITLAB_TOKEN"

[profiles.oss]
provider = "github"
namespaces = ["kubernetes"]
dest = "~/src/oss"
protocol = "https"
```

Then:

```bash
repoclone clone                 # uses the default profile
repoclone clone --profile oss   # uses the oss profile
repoclone config show           # what would apply right now
```

**Precedence:** command-line flags → environment variables → profile → built-in defaults. A flag you do not pass never overwrites your profile.

## How it decides what to do

| Situation | What happens |
|---|---|
| Directory missing | Clone it |
| Clone exists, clean, behind | Fast-forward |
| Clone exists, clean, up to date | Nothing (`unchanged`) |
| Clone exists, **uncommitted changes** | Fetch only, working tree untouched |
| Clone exists, **diverged** | Fetch only, reported as needing a manual merge |
| Directory exists but is not a repository | Skipped, never overwritten |
| Repository is empty | Skipped |

`repoclone` never runs `git merge` unless it can fast-forward, never force-updates, and never deletes anything. `--prune` only *reports* orphans — removing them stays your decision.

## Output formats

`list` and `report` accept `--output table|json|csv`, so results pipe into other tools:

```bash
repoclone list platform --token $T --output json | jq -r '.[].repository'
repoclone report --output csv > inventory.csv
```

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | At least one repository failed, or configuration was invalid |
| `2` | Bad command-line usage (help is printed) |

## Documentation

- [docs/usage.md](docs/usage.md) — task-by-task guide with worked examples
  - [Step-by-step first test](docs/usage.md#step-by-step-first-test) — start here if you have just installed it
- [docs/architecture.md](docs/architecture.md) — how the pieces fit together
- [CONTRIBUTING.md](CONTRIBUTING.md) — how to add a command, a provider or an output format
- [docs/releasing.md](docs/releasing.md) — publishing to PyPI, versioning, trusted publishing

## Licence

MIT.
