# Config - vcspull.config Source: https://vcspull.git-pull.com/api/config/ # Config - `vcspull.config` ```{eval-rst} .. automodule:: vcspull.config :members: :show-inheritance: :undoc-members: ``` --- # Exceptions - vcspull.exc Source: https://vcspull.git-pull.com/api/exc/ # Exceptions - `vcspull.exc` ```{eval-rst} .. automodule:: vcspull.exc :members: :show-inheritance: :undoc-members: ``` --- # API Reference Source: https://vcspull.git-pull.com/api/ (api)= # API Reference :::{seealso} For granular control see the [libvcs documentation](https://libvcs.git-pull.com/en/latest/)---especially the sections on [commands](https://libvcs.git-pull.com/en/latest/usage/commands.html) and [projects](https://libvcs.git-pull.com/en/latest/usage/projects.html). ::: ::::{grid} 1 2 3 3 :gutter: 2 2 3 3 :::{grid-item-card} vcspull.config :link: config :link-type: doc Configuration loading and repo extraction. ::: :::{grid-item-card} vcspull.exc :link: exc :link-type: doc Exception hierarchy. ::: :::{grid-item-card} vcspull.log :link: log :link-type: doc Logging helpers. ::: :::{grid-item-card} vcspull.validator :link: validator :link-type: doc Config validation utilities. ::: :::{grid-item-card} vcspull.util :link: util :link-type: doc General-purpose utilities. ::: :::{grid-item-card} vcspull.types :link: types :link-type: doc Type definitions and aliases. ::: :::: ```{toctree} :hidden: config exc log validator util types ``` --- # Logging - vcspull.log Source: https://vcspull.git-pull.com/api/log/ # Logging - `vcspull.log` ```{eval-rst} .. automodule:: vcspull.log :members: :show-inheritance: :undoc-members: ``` --- # Typings - vcspull.types Source: https://vcspull.git-pull.com/api/types/ # Typings - `vcspull.types` ```{eval-rst} .. automodule:: vcspull.types :members: :show-inheritance: :undoc-members: ``` --- # Utilities - vcspull.util Source: https://vcspull.git-pull.com/api/util/ # Utilities - `vcspull.util` ```{eval-rst} .. automodule:: vcspull.util :members: :show-inheritance: :undoc-members: ``` --- # Validation - vcspull.validator Source: https://vcspull.git-pull.com/api/validator/ # Validation - `vcspull.validator` ```{eval-rst} .. automodule:: vcspull.validator :members: :show-inheritance: :undoc-members: ``` --- # vcspull add Source: https://vcspull.git-pull.com/cli/add/ (cli-add)= # vcspull add The `vcspull add` command registers a repository in your configuration by pointing vcspull at a checkout on disk. The command inspects the directory, merges duplicate workspace roots by default, and prompts before writing unless you pass `--yes`. ```{note} This command replaces the old `vcspull import ` from v1.36--v1.39. For bulk scanning of local repositories, see {ref}`cli-discover`. For bulk import from remote services (GitHub, GitLab, etc.), see {ref}`cli-import`. ``` ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: add ``` ## Basic usage Point to an existing checkout to add it under its parent workspace: ```vcspull-console $ vcspull add ~/study/python/pytest-docker Found new repository to import: + pytest-docker (https://github.com/avast/pytest-docker) • workspace: ~/study/python/ ↳ path: ~/study/python/pytest-docker ? Import this repository? [y/N]: y Successfully added 'pytest-docker' (git+https://github.com/avast/pytest-docker) to ~/.vcspull.yaml under '~/study/python/'. ``` The parent directory (`~/study/python/` in this example) becomes the workspace root. vcspull shortens paths under `$HOME` to `~/...` in its log output so the preview stays readable. ## Overriding detected information ### Choose a different name Override the derived repository name with `--name` when the directory name isn't the label you want stored in the configuration: ```console $ vcspull add ~/study/python/pytest-docker --name docker-pytest ``` ### Override the remote URL vcspull reads the Git `origin` remote automatically. Supply `--url` when you need to register a different remote or when the checkout does not have one yet: ```console $ vcspull add ~/study/python/example --url https://github.com/org/example ``` URLs follow [pip's VCS format][pip vcs url]; vcspull inserts the `git+` prefix for HTTPS URLs so the resulting configuration matches `vcspull fmt` output. ### Select a workspace explicitly The workspace defaults to the checkout's parent directory. Pass `--workspace`/`--workspace-root` to store the repository under a different section: ```console $ vcspull add ~/scratch/tmp-project --workspace ~/projects/python/ ``` ## Confirmation and dry runs `vcspull add` asks for confirmation before writing. Use `--yes` to skip the prompt in automation, or `--dry-run`/`-n` to preview the changes without modifying any files: ```console $ vcspull add ~/study/python/pytest-docker --dry-run ``` Dry runs still show duplicate merge diagnostics so you can see what would change. ## Choosing configuration files vcspull searches for configuration files in this order: 1. `./.vcspull.yaml` 2. `~/.vcspull.yaml` 3. `~/.config/vcspull/*.yaml` Specify a file explicitly with `-f/--file`: ```console $ vcspull add ~/study/python/pytest-docker \ --file ~/configs/python.yaml ``` ## Handling duplicates vcspull merges duplicate workspace sections before writing so existing repositories stay intact. When it collapses multiple sections, the command logs a summary of the merge. Prefer to inspect duplicates yourself? Add `--no-merge` to keep every section untouched. ## Pinned entries Repositories whose configuration includes a pin on the `add` operation are skipped with a warning. For example, given this configuration: ```yaml ~/code/: internal-fork: repo: "git+git@github.com:myorg/internal-fork.git" options: pin: true pin_reason: "pinned to company fork — update manually" ``` Attempting to add a repo that matches an existing pinned entry produces a warning and leaves the entry untouched: ```vcspull-console $ vcspull add ~/code/internal-fork ⚠ Repository 'internal-fork' is pinned (pinned to company fork — update manually) — skipping ``` Both `options.pin: true` (global) and `options.pin.add: true` (per-operation) block the `add` command. The `pin_reason` (if set) is included in the warning. See {ref}`config-pin` for full pin configuration. ## After adding repositories 1. Run `vcspull fmt --write` to normalize your configuration (see {ref}`cli-fmt`). 2. Run `vcspull list` to verify the new entry (see {ref}`cli-list`). 3. Run `vcspull sync` to clone or update the working tree (see {ref}`cli-sync`). ## Migration from the old vcspull import The `vcspull import ` command from v1.36--v1.39 has been replaced by `vcspull add`: ```diff - $ vcspull import flask https://github.com/pallets/flask.git -c ~/.vcspull.yaml + $ vcspull add ~/code/flask --url https://github.com/pallets/flask.git --file ~/.vcspull.yaml ``` Key differences: - `vcspull add` derives the name from the filesystem unless you pass `--name`. - The parent directory becomes the workspace automatically; use `--workspace` to override. - Use `--url` to record a remote when the checkout does not have one. ```{note} Starting with v1.55, `vcspull import` is a *different* command that bulk-imports repositories from remote services (GitHub, GitLab, etc.). See {ref}`cli-import` for details. ``` [pip vcs url]: https://pip.pypa.io/en/stable/topics/vcs-support/ --- # Completions Source: https://vcspull.git-pull.com/cli/completion/ (completion)= (completions)= (cli-completions)= # Completions ## vcspull 1.15+ (experimental) ```{note} See the [shtab library's documentation on shell completion](https://docs.iterative.ai/shtab/use/#cli-usage) for the most up to date way of connecting completion for vcspull. ``` Provisional support for completions in vcspull 1.15+ are powered by [shtab](https://docs.iterative.ai/shtab/). This must be **installed separately**, as it's **not currently bundled with vcspull**. ```console $ pip install shtab --user ``` Or using uv: ```console $ uv tool install shtab ``` For one-time use without installation: ```console $ uvx shtab ``` :::{tab} bash ```console $ shtab --shell=bash -u vcspull.cli.create_parser \ | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/VCSPULL ``` ::: :::{tab} zsh ```console $ shtab --shell=zsh -u vcspull.cli.create_parser \ | sudo tee /usr/local/share/zsh/site-functions/_VCSPULL ``` ::: :::{tab} tcsh ```console $ shtab --shell=tcsh -u vcspull.cli.create_parser \ | sudo tee /etc/profile.d/VCSPULL.completion.csh ``` ::: ## vcspull 0.9 to 1.14 ```{note} See the [click library's documentation on shell completion](https://click.palletsprojects.com/en/8.0.x/shell-completion/) for the most up to date way of connecting completion for vcspull. ``` vcspull 0.9 to 1.14 use [click](https://click.palletsprojects.com)'s completion: :::{tab} bash _~/.bashrc_: ```bash eval "$(_VCSPULL_COMPLETE=bash_source vcspull)" ``` ::: :::{tab} zsh _~/.zshrc_: ```zsh eval "$(_VCSPULL_COMPLETE=zsh_source vcspull)" ``` ::: --- # vcspull discover Source: https://vcspull.git-pull.com/cli/discover/ (cli-discover)= # vcspull discover The `vcspull discover` command scans directories for existing Git repositories and adds them to your vcspull configuration. This is ideal for importing existing workspaces or migrating from other tools. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: discover ``` ## Basic usage Scan a directory for Git repositories: ```vcspull-console $ vcspull discover ~/code Found 2 repositories in ~/code Repository: vcspull Path: ~/code/vcspull Remote: git+https://github.com/vcs-python/vcspull.git Workspace: ~/code/ ? Add to configuration? [y/N]: y Successfully added 'vcspull' to ~/.vcspull.yaml Repository: libvcs Path: ~/code/libvcs Remote: git+https://github.com/vcs-python/libvcs.git Workspace: ~/code/ ? Add to configuration? [y/N]: y Successfully added 'libvcs' to ~/.vcspull.yaml Scan complete: 2 repositories added, 0 skipped ``` The command prompts for each repository before adding it to your configuration. When a matching workspace section already exists, vcspull merges the new entry into it so previously tracked repositories stay intact. Prefer to review duplicates yourself? Add `--no-merge` to keep every section untouched while still seeing a warning. ## Recursive scanning Search nested directories with `--recursive` or `-r`: ```console $ vcspull discover ~/code --recursive ``` This scans all subdirectories for Git repositories, making it ideal for: - Workspaces with project categories (e.g., `~/code/python/`, `~/code/rust/`) - Nested organization structures - Home directory scans ## Unattended mode Skip prompts and add all repositories with `--yes` or `-y`: ```vcspull-console $ vcspull discover ~/code --recursive --yes Found 15 repositories in ~/code Added 15 repositories to ~/.vcspull.yaml ``` This is useful for: - Automated workspace setup - Migration scripts - CI/CD environments ## Dry run mode Preview what would be added without modifying your configuration: ```console $ vcspull discover ~/code --dry-run ``` Output shows: ```vcspull-output Would add: vcspull (~/code/) Remote: git+https://github.com/vcs-python/vcspull.git Would add: libvcs (~/code/) Remote: git+https://github.com/vcs-python/libvcs.git Dry run complete: 2 repositories would be added ``` Combine with `--recursive` to preview large scans: ```console $ vcspull discover ~/ --recursive --dry-run ``` ## Workspace root override Force all discovered repositories to use a specific workspace root: ```console $ vcspull discover ~/company/projects --workspace-root ~/work/ --yes ``` By default, vcspull infers the workspace root from the repository's location. The `--workspace-root` override is useful when: - Consolidating repos from multiple locations - Standardizing workspace organization - The inferred workspace root doesn't match your desired structure Example - scanning home directory but organizing by workspace: ```console $ vcspull discover ~ --recursive --workspace-root ~/code/ --yes ``` ## Choosing configuration files Specify a custom config file with `-f/--file`: ```console $ vcspull discover ~/company \ --recursive \ --file ~/company/.vcspull.yaml ``` If the config file doesn't exist, it will be created. ## Repository detection `vcspull discover` identifies Git repositories by looking for `.git` directories. For each repository found: 1. The directory name becomes the repository name 2. The `origin` remote URL is extracted (if available) 3. The workspace root is inferred from the repository's location 4. You're prompted to confirm adding it ### Repositories without remotes Repositories without an `origin` remote are detected but logged as a warning: ```vcspull-console $ vcspull discover ~/code WARNING: Could not determine remote URL for ~/code/local-project (no origin remote) Skipping local-project ``` These repositories are skipped by default. You can add them manually with `vcspull add` if needed. ## Examples Scan current directory: ```console $ vcspull discover . ``` Scan recursively with confirmation: ```console $ vcspull discover ~/code --recursive ``` Bulk import without prompts: ```console $ vcspull discover ~/code --recursive --yes ``` Preview a large scan: ```console $ vcspull discover ~/code --recursive --dry-run ``` Scan with custom workspace: ```console $ vcspull discover /tmp/checkouts --workspace-root ~/code/ --yes ``` Scan to specific config: ```console $ vcspull discover ~/company/repos \ --recursive \ --yes \ --file ~/company/.vcspull.yaml ``` ## After discovering repositories After discovering repositories, consider: 1. Running `vcspull fmt --write` to normalize and sort your configuration (see {ref}`cli-fmt`) 2. Running `vcspull list --tree` to verify the workspace organization (see {ref}`cli-list`) 3. Running `vcspull status` to confirm all repositories are tracked (see {ref}`cli-status`) ## Handling existing entries If a repository already exists in your configuration, vcspull will detect it: ```vcspull-console Repository: flask Path: ~/code/flask Remote: git+https://github.com/pallets/flask.git Workspace: ~/code/ Note: Repository 'flask' already exists in ~/code/ ? Add anyway? [y/N]: n Skipped flask (already exists) ``` You can choose to skip or overwrite the existing entry. ## Pinned entries Repositories pinned for the `discover` operation are silently skipped during scanning. For example, given this configuration: ```yaml ~/code/: internal-fork: repo: "git+git@github.com:myorg/internal-fork.git" options: pin: discover: true pin_reason: "pinned to company fork — update manually" ``` When `vcspull discover` encounters `internal-fork` on disk, it groups it with existing entries and does not prompt for it. A debug-level log message is emitted, so pass `--log-level debug` to see which entries were skipped due to pins: ```console $ vcspull discover ~/code \ --recursive \ --log-level debug ``` Both `options.pin: true` (global) and `options.pin.discover: true` (per-operation) block discovery. See {ref}`config-pin` for full pin configuration. ## Migration from vcspull import --scan If you previously used `vcspull import --scan`: ```diff - $ vcspull import --scan ~/code --recursive -c ~/.vcspull.yaml --yes + $ vcspull discover ~/code --recursive --file ~/.vcspull.yaml --yes ``` Changes: - Command: `import --scan` → `discover` - Config flag: `-c` → `-f` - `--scan` flag removed (discover always scans) - Same functionality otherwise ## Use cases **Initial workspace setup:** Discover all repositories: ```console $ vcspull discover ~/code --recursive --yes ``` Then format and sort the configuration: ```console $ vcspull fmt --write ``` **Migrate from another tool:** Preview what would be discovered: ```console $ vcspull discover ~/projects --recursive --dry-run ``` Then apply the changes: ```console $ vcspull discover ~/projects --recursive --yes ``` **Add company repos to separate config:** ```console $ vcspull discover ~/company \ --recursive \ --file ~/company/.vcspull.yaml \ --workspace-root ~/work/ \ --yes ``` **Audit what's on disk:** ```console $ vcspull discover ~/code --recursive --dry-run | grep "Would add" ``` --- # vcspull fmt Source: https://vcspull.git-pull.com/cli/fmt/ (cli-fmt)= # vcspull fmt `vcspull fmt` normalizes configuration files so directory keys and repository entries stay consistent. By default the formatter prints the proposed changes to stdout. Apply the updates in place with `--write`. When duplicate workspace roots are encountered, the formatter merges them into a single section so repositories are never dropped. Prefer to review duplicates without rewriting them? Pass `--no-merge` to leave the original sections in place while still showing a warning. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: fmt ``` ## What gets formatted The formatter performs four main tasks: - Expands string-only entries into verbose dictionaries using the `repo` key. - Converts legacy `url` keys to `repo` for consistency with the rest of the tooling. - Sorts directory keys and repository names alphabetically to minimize diffs. - Consolidates duplicate workspace roots into a single merged section while logging any conflicts. ### Pinned entries Repositories pinned for the `fmt` operation are preserved **verbatim** — no normalization, no key reordering, no string-to-dict expansion. For example, given this configuration: ```yaml ~/code/: pinned-dep: url: git+https://corp.example.com/team/pinned-dep.git options: pin: fmt: true libvcs: git+https://github.com/vcspull/libvcs.git ``` After `vcspull fmt --write`, only `libvcs` is normalized. The `pinned-dep` entry keeps its original `url` key and field order: ```yaml ~/code/: libvcs: repo: git+https://github.com/vcspull/libvcs.git pinned-dep: url: git+https://corp.example.com/team/pinned-dep.git options: pin: fmt: true ``` See {ref}`config-pin` for full pin configuration. For example: ```yaml ~/code/: libvcs: git+https://github.com/vcspull/libvcs.git vcspull: url: git+https://github.com/vcspull/vcspull.git ``` becomes: ```yaml ~/code/: libvcs: repo: git+https://github.com/vcspull/libvcs.git vcspull: repo: git+https://github.com/vcspull/vcspull.git ``` ## Writing changes Run the formatter in dry-run mode first to preview the adjustments: ```console $ vcspull fmt --file ~/.vcspull.yaml ``` Then add `--write` to persist them back to disk: ```console $ vcspull fmt \ --file ~/.vcspull.yaml \ --write ``` Use `--all` to iterate over the default search locations: the current working directory, `~/.vcspull.*`, and the XDG configuration directory. Each formatted file is reported individually. ```console $ vcspull fmt --all --write ``` Pair the formatter with [`vcspull discover`](cli-discover) after scanning the file system to keep newly added repositories ordered and normalized. --- # vcspull import codeberg Source: https://vcspull.git-pull.com/cli/import/codeberg/ (cli-import-codeberg)= # vcspull import codeberg Import repositories from Codeberg. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import codeberg ``` ## Authentication - **Env vars**: `CODEBERG_TOKEN` (primary), `GITEA_TOKEN` (fallback) - **Token type**: API token - **Scope**: no scopes needed for public repos; token required for private repos - **Create at**: Set the token: ```console $ export CODEBERG_TOKEN=... ``` Then import: ```console $ vcspull import codeberg myuser --workspace ~/code/ ``` --- # vcspull import codecommit Source: https://vcspull.git-pull.com/cli/import/codecommit/ (cli-import-codecommit)= # vcspull import codecommit Import repositories from AWS CodeCommit. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import codecommit ``` ## Usage CodeCommit does not require a target argument. Use `--region` and `--profile` to select the AWS environment: ```console $ vcspull import codecommit \ --workspace ~/code/ \ --region us-east-1 \ --profile work ``` ## Authentication - **Auth**: AWS CLI credentials (`aws configure`) — no token env var - **CLI args**: `--region`, `--profile` - **IAM permissions required**: - `codecommit:ListRepositories` (resource: `*`) - `codecommit:BatchGetRepositories` (resource: repo ARNs or `*`) - **Dependency**: AWS CLI must be installed (`pip install awscli`) Configure your AWS credentials: ```console $ aws configure ``` Then import: ```console $ vcspull import codecommit \ --workspace ~/code/ \ --region us-east-1 ``` --- # vcspull import forgejo Source: https://vcspull.git-pull.com/cli/import/forgejo/ (cli-import-forgejo)= # vcspull import forgejo Import repositories from a self-hosted Forgejo instance. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import forgejo ``` ## Authentication - **Env vars**: `FORGEJO_TOKEN` (primary; matched when hostname contains "forgejo"), `GITEA_TOKEN` (fallback) - **Token type**: API token - **Scope**: `read:repository` - **Create at**: `https:///user/settings/applications` Set the token: ```console $ export FORGEJO_TOKEN=... ``` Then import: ```console $ vcspull import forgejo myuser \ --workspace ~/code/ \ --url https://forgejo.example.com ``` --- # vcspull import gitea Source: https://vcspull.git-pull.com/cli/import/gitea/ (cli-import-gitea)= # vcspull import gitea Import repositories from a self-hosted Gitea instance. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import gitea ``` ## Authentication - **Env var**: `GITEA_TOKEN` - **Token type**: API token with scoped permissions - **Scope**: `read:repository` (minimum for listing repos) - **Create at**: `https:///user/settings/applications` Set the token: ```console $ export GITEA_TOKEN=... ``` Then import: ```console $ vcspull import gitea myuser \ --workspace ~/code/ \ --url https://git.example.com ``` --- # vcspull import github Source: https://vcspull.git-pull.com/cli/import/github/ (cli-import-github)= # vcspull import github Import repositories from GitHub or GitHub Enterprise. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import github ``` ## Authentication - **Env vars**: `GITHUB_TOKEN` (primary), `GH_TOKEN` (fallback) - **Token type**: Personal access token (classic) or fine-grained PAT - **Permissions**: - Classic PAT: no scopes needed for public repos; `repo` scope for private repos; `read:org` for org repos - Fine-grained PAT: "Metadata: Read-only" for public; add "Contents: Read-only" for private - **Create at**: Set the token: ```console $ export GITHUB_TOKEN=ghp_... ``` Then import: ```console $ vcspull import gh myuser --workspace ~/code/ ``` --- # vcspull import gitlab Source: https://vcspull.git-pull.com/cli/import/gitlab/ (cli-import-gitlab)= # vcspull import gitlab Import repositories from GitLab or a self-hosted GitLab instance. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import gitlab ``` ## Subgroup targeting Use slash notation to target a specific subgroup or sub-subgroup directly: ```console $ vcspull import gl my-group/my-subgroup \ --mode org \ --workspace ~/code/ ``` ```console $ vcspull import gl my-group/my-subgroup/my-leaf \ --mode org \ --workspace ~/code/ ``` The `TARGET` argument accepts any depth of slash-separated group path. ## Group flattening When importing a GitLab group with `--mode org`, vcspull preserves subgroup structure as nested workspace directories by default. Use `--flatten-groups` to place all repositories directly in the base workspace: ```console $ vcspull import gl my-group \ --mode org \ --workspace ~/code/ \ --flatten-groups ``` ### Workspace structure by target and flag Given a group tree `my-group → sub → leaf`, importing from `~/code/`: | Target | `--flatten-groups` | Workspace sections written | |--------|:-----------------:|---------------------------| | `my-group` | no | `~/code/`, `~/code/sub/`, `~/code/sub/leaf/` | | `my-group` | yes | `~/code/` only | | `my-group/sub` | no | `~/code/`, `~/code/leaf/` | | `my-group/sub` | yes | `~/code/` only | | `my-group/sub/leaf` | no | `~/code/` only (leaf — no further nesting) | | `my-group/sub/leaf` | yes | `~/code/` only | When the target is already the deepest group (a leaf), `--flatten-groups` has no effect — all repositories already land in the base workspace. ## Including shared repositories By default, repositories shared into a group from another namespace are excluded. Use `--with-shared` to include them when importing in `--mode org`: ```console $ vcspull import gl my-group \ --mode org \ --workspace ~/code/ \ --with-shared ``` `--with-shared` has no effect in user mode or search mode; a warning is emitted if the flag is passed in those contexts. ## Skipping subgroups Use `--skip-group` to exclude all repositories whose owner path contains a specific group name segment. Matching is case-insensitive and segment-based: ```console $ vcspull import gl my-group \ --mode org \ --workspace ~/code/ \ --skip-group bots ``` Repeat the flag to skip multiple groups: ```console $ vcspull import gl my-group \ --mode org \ --workspace ~/code/ \ --skip-group bots \ --skip-group archived ``` The flag matches any path segment: `--skip-group bots` skips repos owned by `my-group/bots` or `my-group/bots/subteam` but not `my-group/robotics`. > **Note**: Passing the root group name itself (`--skip-group my-org` when > importing `my-org`) will silently exclude every repository, since all repo > paths include the root group as a path segment. ## Authentication - **Env vars**: `GITLAB_TOKEN` (primary), `GL_TOKEN` (fallback) - **Token type**: Personal access token - **Scope**: `read_api` (minimum for listing projects; **required** for search mode) - **Create at**: (self-hosted: `https:///-/user_settings/personal_access_tokens`) Set the token: ```console $ export GITLAB_TOKEN=glpat-... ``` Then import: ```console $ vcspull import gl myuser --workspace ~/code/ ``` --- # vcspull import Source: https://vcspull.git-pull.com/cli/import/ (cli-import)= # vcspull import The `vcspull import` command bulk-imports repositories from remote hosting services into your vcspull configuration. It connects to the service API, fetches a list of repositories, and writes them to your config file in a single step. Supported services: **GitHub**, **GitLab**, **Codeberg**, **Gitea**, **Forgejo**, and **AWS CodeCommit**. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: import :nosubcommands: :nodescription: ``` Choose a service subcommand for details: - {ref}`cli-import-github` — GitHub or GitHub Enterprise - {ref}`cli-import-gitlab` — GitLab (gitlab.com or self-hosted) - {ref}`cli-import-codeberg` — Codeberg - {ref}`cli-import-gitea` — Self-hosted Gitea instance - {ref}`cli-import-forgejo` — Self-hosted Forgejo instance - {ref}`cli-import-codecommit` — AWS CodeCommit ## Basic usage Import all repositories for a GitHub user into a workspace: ```vcspull-console $ vcspull import github myuser --workspace ~/code/ → Fetching repositories from GitHub... ✓ Found 12 repositories + project-a [Python] + project-b [Rust] ★42 + dotfiles ... and 9 more Import 12 repositories to ~/.vcspull.yaml? [y/N]: y ✓ Added 12 repositories to ~/.vcspull.yaml ``` ## Supported services | Service | Aliases | Self-hosted | Auth env var(s) | |------------|------------------|----------------------|-----------------------------------| | GitHub | `github`, `gh` | `--url` | `GITHUB_TOKEN` / `GH_TOKEN` | | GitLab | `gitlab`, `gl` | `--url` | `GITLAB_TOKEN` / `GL_TOKEN` | | Codeberg | `codeberg`, `cb` | No | `CODEBERG_TOKEN` / `GITEA_TOKEN` | | Gitea | `gitea` | `--url` (required) | `GITEA_TOKEN` | | Forgejo | `forgejo` | `--url` (required) | `FORGEJO_TOKEN` / `GITEA_TOKEN` | | CodeCommit | `codecommit`, `cc`, `aws` | N/A | AWS CLI credentials | For Gitea and Forgejo, `--url` is required because there is no default instance. ```{toctree} :maxdepth: 1 :hidden: github gitlab codeberg gitea forgejo codecommit ``` ## Import modes ### User mode (default) Fetch all repositories owned by a user: ```console $ vcspull import gh myuser --workspace ~/code/ ``` ### Organization mode Fetch repositories belonging to an organization or group: ```console $ vcspull import gh my-org \ --mode org \ --workspace ~/code/ ``` For GitLab, subgroups are supported with slash notation: ```console $ vcspull import gl my-group/sub-group \ --mode org \ --workspace ~/code/ ``` ### Search mode Search for repositories matching a query: ```console $ vcspull import gh django \ --mode search \ --workspace ~/code/ \ --min-stars 100 ``` ## Filtering Narrow results with filtering flags: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --language python ``` ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --topics cli,automation ``` ```console $ vcspull import gh django \ --mode search \ --workspace ~/code/ \ --min-stars 50 ``` Include archived or forked repositories (excluded by default): ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --archived \ --forks ``` Limit the number of repositories fetched: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --limit 50 ``` ```{note} Not all filters work with every service. For example, `--language` may not return results for GitLab or CodeCommit because those APIs don't expose language metadata. vcspull warns when a filter is unlikely to work. ``` ## Output formats Human-readable output (default): ```console $ vcspull import gh myuser --workspace ~/code/ ``` JSON for automation: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --json ``` NDJSON for streaming: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --ndjson ``` ## Dry runs and confirmation Preview what would be imported without writing to the config file: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --dry-run ``` Skip the confirmation prompt (useful for scripts): ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --yes ``` ## Syncing existing entries By default, repositories that already exist in your configuration are **skipped** — even if the remote URL has changed. This prevents accidental updates when re-importing from a service. For example, suppose your team migrated from HTTPS to SSH. Without `--sync`, the old HTTPS URLs stay in your config: ```vcspull-console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ → Fetching repositories from GitHub... ✓ Found 8 repositories + new-project [Python] ⊘ api-server (already in config) ⊘ web-frontend (already in config) ... and 5 more Import 1 new repository to ~/.vcspull.yaml? [y/N]: y ✓ Added 1 repository to ~/.vcspull.yaml ! Skipped 7 existing repositories ``` Pass `--sync` to fully reconcile your config with the remote — update changed URLs, and remove entries no longer on the remote: ```console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --sync ``` `--sync` does three things: 1. **Add** new repositories (same as without `--sync`) 2. **Update URLs** for existing entries whose URL has changed 3. **Prune** entries that are no longer on the remote When syncing, vcspull replaces only the `repo` URL. All other metadata is preserved: - `options` (including pins) - `remotes` - `shell_command_after` - `worktrees` For example, given this config before the import: ```yaml ~/code/: api-server: repo: "git+https://github.com/myorg/api-server.git" remotes: upstream: "git+https://github.com/upstream/api-server.git" shell_command_after: - make setup ``` After `vcspull import gh myorg --workspace ~/code/ --sync`, the `repo` URL is updated to SSH while `remotes` and `shell_command_after` are kept: ```yaml ~/code/: api-server: repo: "git+git@github.com:myorg/api-server.git" remotes: upstream: "git+https://github.com/upstream/api-server.git" shell_command_after: - make setup ``` ### Provenance tracking When `--sync` (or `--prune`) is used, vcspull tags each imported repo with a `metadata.imported_from` field recording the import source: ```yaml ~/code/: api-server: repo: "git+git@github.com:myorg/api-server.git" metadata: imported_from: "github:myorg" ``` The tag format is `"{service}:{target}"` — e.g. `"github:myorg"`, `"gitlab:mygroup"`, `"codeberg:myuser"`. Provenance tags scope the prune step: only entries tagged with the **same** import source are candidates for removal. This means: - Manually added repos (no `metadata.imported_from`) are never pruned - Repos imported from a different source (e.g. `"github:other-org"`) are never pruned when syncing `"github:myorg"` Pruning is **config-only** — cloned directories on disk are not deleted. ### Pruning stale entries To remove stale entries without updating URLs, use `--prune`: ```console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --prune ``` Preview what would be pruned with `--dry-run`: ```console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --prune \ --dry-run ``` `--sync` and `--prune` can be combined — `--sync` alone already includes pruning, so `--sync --prune` behaves identically to `--sync`. | Flags | Add new | Update URLs | Prune stale | Prune untracked | |-------|---------|-------------|-------------|-----------------| | (none) | yes | no | no | no | | `--sync` | yes | yes | yes | no | | `--prune` | yes | no | yes | no | | `--sync --prune` | yes | yes | yes | no | | `--sync --prune-untracked` | yes | yes | yes | yes | | `--prune --prune-untracked` | yes | no | yes | yes | ### Pruning untracked entries Standard `--sync` / `--prune` only removes entries tagged with the current import source. Manually added repos — entries without any `metadata.imported_from` tag — are left untouched. To also remove these "untracked" entries, add `--prune-untracked`: ```console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --sync \ --prune-untracked ``` `--prune-untracked` requires `--sync` or `--prune` — it cannot be used alone. Safety rails: - **Pinned entries** are always preserved (regardless of provenance) - **Entries tagged from a different source** (e.g. `"gitlab:other"`) are preserved — they are "tracked" by that other import - **Only workspaces the import targets** are scanned — entries in other workspaces are untouched - A **confirmation prompt** lists exactly what would be removed before proceeding (use `--yes` to skip, `--dry-run` to preview) Preview with dry-run: ```console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --prune \ --prune-untracked \ --dry-run ``` ### Pin-aware behavior Repositories protected by a pin are **exempt** from both URL updates and pruning. The following configurations all prevent `--sync` from modifying an entry: - `options.pin: true` — blocks all operations - `options.pin.import: true` — blocks import only - `options.allow_overwrite: false` — shorthand for `pin: {import: true}` Pinned repositories are skipped with an informational message showing the `pin_reason` (if set): ```vcspull-console $ vcspull import gh myorg \ --mode org \ --workspace ~/code/ \ --sync → Fetching repositories from GitHub... ✓ Found 8 repositories ↻ api-server (URL changed) ⊘ internal-fork (pinned to company mirror) ... and 6 more Import 7 repositories to ~/.vcspull.yaml? [y/N]: y ✓ Updated 6 repositories in ~/.vcspull.yaml ! Skipped 1 pinned repository ``` For example, this entry cannot be updated or pruned regardless of `--sync`: ```yaml ~/code/: internal-fork: repo: "git+ssh://git@corp.example.com/team/internal-fork.git" options: pin: import: true pin_reason: "pinned to company mirror — update manually" ``` See {ref}`config-pin` for full pin configuration. ## Configuration file selection vcspull writes to `~/.vcspull.yaml` by default. Override with `-f/--file`: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --file ~/configs/github.yaml ``` ## Protocol selection SSH clone URLs are used by default. Switch to HTTPS with `--https`: ```console $ vcspull import gh myuser \ --workspace ~/code/ \ --https ``` ## Self-hosted instances Point to a self-hosted GitHub Enterprise, GitLab, Gitea, or Forgejo instance with `--url`: ```console $ vcspull import gitea myuser \ --workspace ~/code/ \ --url https://git.example.com ``` ## Authentication vcspull reads API tokens from environment variables. Use `--token` to override. Environment variables are preferred for security. See each service page for details. | Service | Env var(s) | Token type | Min scope / permissions | |------------|----------------------------------|-----------------------|------------------------------------------------------------------| | GitHub | `GITHUB_TOKEN` / `GH_TOKEN` | PAT (classic or fine) | None (public), `repo` (private) | | GitLab | `GITLAB_TOKEN` / `GL_TOKEN` | PAT | `read_api` | | Codeberg | `CODEBERG_TOKEN` / `GITEA_TOKEN` | API token | None (public), any token (private) | | Gitea | `GITEA_TOKEN` | API token | `read:repository` | | Forgejo | `FORGEJO_TOKEN` / `GITEA_TOKEN` | API token | `read:repository` | | CodeCommit | AWS CLI credentials | IAM access key | `codecommit:ListRepositories`, `codecommit:BatchGetRepositories` | ## After importing 1. Run `vcspull fmt --write` to normalize and sort the configuration (see {ref}`cli-fmt`). 2. Run `vcspull list` to verify the imported entries (see {ref}`cli-list`). 3. Run `vcspull sync` to clone the repositories (see {ref}`cli-sync`). --- # CLI Reference Source: https://vcspull.git-pull.com/cli/ (cli)= (commands)= # CLI Reference ::::{grid} 1 2 3 3 :gutter: 2 2 3 3 :::{grid-item-card} vcspull sync :link: sync :link-type: doc Pull / clone repositories from config. ::: :::{grid-item-card} vcspull add :link: add :link-type: doc Add a repository to your config file. ::: :::{grid-item-card} vcspull list :link: list :link-type: doc List configured repositories. ::: :::{grid-item-card} vcspull search :link: search :link-type: doc Search repos by name, path, or URL. ::: :::{grid-item-card} vcspull status :link: status :link-type: doc Show working-tree status for each repo. ::: :::{grid-item-card} vcspull discover :link: discover :link-type: doc Scan directories for existing repos. ::: :::{grid-item-card} vcspull fmt :link: fmt :link-type: doc Normalize and format config files. ::: :::{grid-item-card} vcspull import :link: import/index :link-type: doc Import repos from GitHub, GitLab, and more. ::: :::{grid-item-card} vcspull worktree :link: worktree/index :link-type: doc Manage git worktrees declaratively. ::: :::{grid-item-card} Completion :link: completion :link-type: doc Shell completions for bash, zsh, and fish. ::: :::: ```{toctree} :caption: General commands :maxdepth: 1 sync add import/index discover list search status worktree/index fmt ``` ```{toctree} :caption: Completion :maxdepth: 1 completion ``` (cli-main)= (vcspull-main)= ## Command: `vcspull` ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :nosubcommands: subparser_name : @replace See :ref:`cli-sync`, :ref:`cli-add`, :ref:`cli-import`, :ref:`cli-discover`, :ref:`cli-list`, :ref:`cli-search`, :ref:`cli-status`, :ref:`cli-worktree`, :ref:`cli-fmt` ``` --- # vcspull list Source: https://vcspull.git-pull.com/cli/list/ (cli-list)= # vcspull list The `vcspull list` command displays configured repositories from your vcspull configuration files. Use this introspection command to verify your configuration, filter repositories by patterns, and export structured data for automation. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: list ``` ## Basic usage List all configured repositories: ```vcspull-console $ vcspull list • tiktoken → ~/study/ai/tiktoken • GeographicLib → ~/study/c++/GeographicLib • flask → ~/code/flask ``` ## Filtering repositories Filter repositories using fnmatch-style patterns: ```vcspull-console $ vcspull list 'flask*' • flask → ~/code/flask • flask-sqlalchemy → ~/code/flask-sqlalchemy ``` Multiple patterns are supported: ```console $ vcspull list django flask ``` ## Tree view Group repositories by workspace root with `--tree`: ```vcspull-console $ vcspull list --tree ~/study/ai/ • tiktoken → ~/study/ai/tiktoken ~/study/c++/ • GeographicLib → ~/study/c++/GeographicLib • anax → ~/study/c++/anax ~/code/ • flask → ~/code/flask ``` ## JSON output Export repository information as JSON for automation and tooling: ```console $ vcspull list --json ``` Output format: ```json [ { "name": "tiktoken", "url": "git+https://github.com/openai/tiktoken.git", "path": "~/study/ai/tiktoken", "workspace_root": "~/study/ai/" }, { "name": "flask", "url": "git+https://github.com/pallets/flask.git", "path": "~/code/flask", "workspace_root": "~/code/" } ] ``` The `workspace_root` field shows which configuration section the repository belongs to, matching the keys in your `.vcspull.yaml` file. Filter JSON output with tools like [jq]: ```console $ vcspull list --json | jq '.[] | select(.workspace_root | contains("study"))' ``` ## NDJSON output For streaming and line-oriented processing, use `--ndjson`: ```console $ vcspull list --ndjson {"name":"tiktoken","url":"git+https://github.com/openai/tiktoken.git","path":"~/study/ai/tiktoken","workspace_root":"~/study/ai/"} {"name":"flask","url":"git+https://github.com/pallets/flask.git","path":"~/code/flask","workspace_root":"~/code/"} ``` Each line is a complete JSON object, making it ideal for: - Processing large configurations line-by-line - Streaming data to other tools - Parsing with simple line-based tools ```console $ vcspull list --ndjson | grep 'study' | jq -r '.name' ``` ## Choosing configuration files By default, vcspull searches for config files in standard locations (`~/.vcspull.yaml`, `./.vcspull.yaml`, and XDG config directories). Specify a custom config file with `-f/--file`: ```console $ vcspull list --file ~/projects/.vcspull.yaml ``` ## Workspace filtering Filter repositories by workspace root with `-w/--workspace/--workspace-root`: ```vcspull-console $ vcspull list --workspace ~/code/ • flask → ~/code/flask • requests → ~/code/requests ``` Globbing is supported, so you can target multiple related workspaces: ```console $ vcspull list --workspace '*/work/*' ``` The workspace filter combines with pattern filters and structured output flags, allowing you to export subsets of your configuration quickly. ## Color output Control colored output with `--color`: - `--color auto` (default): Use colors if outputting to a terminal - `--color always`: Always use colors - `--color never`: Never use colors The `NO_COLOR` environment variable is also respected. [jq]: https://stedolan.github.io/jq/ --- # vcspull search Source: https://vcspull.git-pull.com/cli/search/ (cli-search)= # vcspull search The `vcspull search` command looks up repositories across your vcspull configuration with an rg-like query syntax. Queries are regex by default, can scope to specific fields, and can emit structured JSON for automation. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: search ``` ## Basic usage Search all fields (name, path, url, workspace) with regex: ```vcspull-console $ vcspull search django • django → ~/code/django ``` ## Field-scoped queries Target specific fields with prefixes: ```vcspull-console $ vcspull search name:django url:github • django → ~/code/django url: git+https://github.com/django/django.git ``` Available field prefixes: - `name:` - `path:` - `url:` - `workspace:` (alias: `root:` or `ws:`) ## Literal matches Use `-F/--fixed-strings` to match literal text instead of regex: ```console $ vcspull search --fixed-strings 'git+https://github.com/org/repo.git' ``` ## Case handling `-i/--ignore-case` forces case-insensitive matching. `-S/--smart-case` matches case-insensitively unless your query includes uppercase characters. ```console $ vcspull search --smart-case Django ``` ## Boolean matching By default all terms must match. Use `--any` to match if *any* term matches: ```console $ vcspull search --any django flask ``` Invert matches with `-v/--invert-match`: ```console $ vcspull search --invert-match --fixed-strings github ``` ## JSON output Emit matches as JSON for automation: ```console $ vcspull search --json django ``` Output format: ```json [ { "name": "django", "url": "git+https://github.com/django/django.git", "path": "~/code/django", "workspace_root": "~/code/", "matched_fields": ["name", "url"] } ] ``` Use NDJSON for streaming: ```console $ vcspull search --ndjson django ``` --- # vcspull status Source: https://vcspull.git-pull.com/cli/status/ (cli-status)= # vcspull status The `vcspull status` command checks the health of configured repositories, showing which repositories exist on disk, which are missing, and their Git status. This introspection command helps verify your local workspace matches your configuration. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: status ``` ## Basic usage Check the status of all configured repositories: ```vcspull-console $ vcspull status ✗ tiktoken: missing ✓ flask: up to date ✓ django: up to date Summary: 3 repositories, 2 exist, 1 missing ``` The command shows: - Repository name and path - Whether the repository exists on disk - If it's a Git repository - Basic cleanliness status ## Filtering repositories Filter repositories using fnmatch-style patterns: ```vcspull-console $ vcspull status 'django*' • django → ~/code/django (exists, clean) • django-extensions → ~/code/django-extensions (missing) ``` Multiple patterns are supported: ```console $ vcspull status django flask requests ``` ## Detailed status Show additional information with `--detailed` or `-d`: ```vcspull-console $ vcspull status --detailed ✓ flask: up to date Path: ~/code/flask Branch: main Ahead/Behind: 0/0 ``` This mode shows the full path, active branch, and divergence counters (`ahead` and `behind`) relative to the tracked upstream. If the working tree has uncommitted changes the headline reports `dirty` and the JSON payloads set `clean` to `false`. ## JSON output Export status information as JSON for automation and monitoring: ```console $ vcspull status --json ``` Output format: ```json [ { "reason": "status", "name": "tiktoken", "path": "~/study/ai/tiktoken", "workspace_root": "~/study/ai/", "exists": false, "is_git": false, "clean": null, "branch": null, "ahead": null, "behind": null }, { "reason": "status", "name": "flask", "path": "~/code/flask", "workspace_root": "~/code/", "exists": true, "is_git": true, "clean": true, "branch": "main", "ahead": 0, "behind": 0 }, { "reason": "summary", "total": 2, "exists": 1, "missing": 1, "clean": 1, "dirty": 0 } ] ``` Each status entry includes: - `reason`: Always `"status"` for repository entries, `"summary"` for the final summary - `name`: Repository name - `path`: Full filesystem path - `workspace_root`: Configuration section this repo belongs to - `exists`: Whether the directory exists - `is_git`: Whether it's a Git repository - `clean`: Git working tree status (`null` if not a git repo or missing) - `branch`: Current branch (when detailed information is available) - `ahead`, `behind`: Divergence counts relative to the upstream branch Filter with [jq] to find missing repositories: ```console $ vcspull status --json \ | jq '.[] | select(.reason == "status" and .exists == false)' ``` Or extract just the summary: ```console $ vcspull status --json | jq '.[] | select(.reason == "summary")' ``` ## NDJSON output For streaming output, use `--ndjson`: ```console $ vcspull status --ndjson {"reason":"status","name":"tiktoken","path":"~/study/ai/tiktoken","workspace_root":"~/study/ai/","exists":false,"is_git":false,"clean":null} {"reason":"status","name":"flask","path":"~/code/flask","workspace_root":"~/code/","exists":true,"is_git":true,"clean":true} {"reason":"summary","total":2,"exists":1,"missing":1,"clean":1,"dirty":0} ``` Process line-by-line: ```console $ vcspull status --ndjson | grep '"exists":false' | jq -r '.name' ``` ## Use cases Monitor missing repositories: ```console $ vcspull status --json \ | jq -r '.[] | select(.reason == "status" and .exists == false) | .name' ``` Check which repositories need syncing: ```console $ vcspull status --json \ | jq -r '.[] | select(.reason == "status" and .exists == false) | .name' \ | xargs vcspull sync ``` Generate reports: ```console $ vcspull status --json > workspace-status-$(date +%Y%m%d).json ``` ## Choosing configuration files Specify a custom config file with `-f/--file`: ```console $ vcspull status --file ~/projects/.vcspull.yaml ``` ## Workspace filtering Filter repositories by workspace root (planned feature): ```console $ vcspull status --workspace ~/code/ ``` ## Color output Control colored output with `--color`: - `--color auto` (default): Use colors if outputting to a terminal - `--color always`: Always use colors - `--color never`: Never use colors The `NO_COLOR` environment variable is also respected. ## Future enhancements The status command will be expanded to include: - Detailed Git status (ahead/behind remote, current branch) - Dirty working tree detection - Remote URL mismatches - Submodule status [jq]: https://stedolan.github.io/jq/ --- # vcspull sync Source: https://vcspull.git-pull.com/cli/sync/ (cli-sync)= (vcspull-sync)= # vcspull sync The `vcspull sync` command clones and updates repositories defined in your vcspull configuration. It's the primary command for keeping your local workspace synchronized with remote repositories. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: sync ``` ## Dry run mode Preview what would be synchronized without making changes: ```vcspull-console $ vcspull sync --dry-run '*' Would sync flask at ~/code/flask Would sync django at ~/code/django Would sync requests at ~/code/requests ``` Use `--dry-run` or `-n` to: - Verify your configuration before syncing - Check which repositories would be updated - Test pattern filters - Preview operations in CI/CD ## JSON output Export sync operations as JSON for automation: ```console $ vcspull sync --dry-run --json '*' [ { "reason": "sync", "name": "flask", "path": "~/code/flask", "workspace_root": "~/code/", "status": "preview" }, { "reason": "summary", "total": 3, "synced": 0, "previewed": 3, "failed": 0 } ] ``` Each event emitted during the run includes: - `reason`: `"sync"` for repository events, `"summary"` for the final summary - `name`, `path`, `workspace_root`: Repository metadata from your config - `status`: `"synced"`, `"preview"`, or `"error"` (with an `error` field) Use `--json` without `--dry-run` to capture actual sync executions—successful and failed repositories are emitted with their final state. ## NDJSON output Stream sync events line-by-line with `--ndjson`: ```console $ vcspull sync --dry-run --ndjson '*' {"reason":"sync","name":"flask","path":"~/code/flask","workspace_root":"~/code/","status":"preview"} {"reason":"summary","total":3,"synced":0,"previewed":3,"failed":0} ``` Each line is a JSON object representing a sync event, ideal for: - Real-time processing - Progress monitoring - Log aggregation ## Configuration file selection Specify a custom config file with `-f/--file`: ```console $ vcspull sync --file ~/projects/.vcspull.yaml '*' ``` By default, vcspull searches for config files in: 1. Current directory (`.vcspull.yaml`) 2. Home directory (`~/.vcspull.yaml`) 3. XDG config directory (`~/.config/vcspull/`) ## Workspace filtering Filter repositories by workspace root with `-w/--workspace` or `--workspace-root`: ```console $ vcspull sync --workspace ~/code/ '*' ``` This syncs only repositories in the specified workspace root, useful for: - Selective workspace updates - Multi-workspace setups - Targeted sync operations All three flag names work identically. Using `--workspace`: ```console $ vcspull sync --workspace ~/code/ '*' ``` Or using `--workspace-root`: ```console $ vcspull sync --workspace-root ~/code/ '*' ``` ## Color output Control colored output with `--color`: - `--color auto` (default): Use colors if outputting to a terminal - `--color always`: Always use colors - `--color never`: Never use colors The `NO_COLOR` environment variable is also respected. ## Filtering repos As of 1.13.x, `$ vcspull sync` with no args passed will show a help dialog: ```console $ vcspull sync Usage: vcspull sync [OPTIONS] [REPO_TERMS]... ``` ### Sync all repos Depending on how your terminal works with shell escapes for expands such as the [wild card / asterisk], you may not need to quote `*`. ```console $ vcspull sync '*' ``` [wild card / asterisk]: https://tldp.org/LDP/abs/html/special-chars.html#:~:text=wild%20card%20%5Basterisk%5D. ### Filtering Filter all repos start with "django-": ```console $ vcspull sync 'django-*' ``` ### Multiple terms Filter all repos start with "django-": ```console $ vcspull sync 'django-anymail' 'django-guardian' ``` ## Error handling ### Repos not found in config As of 1.13.x, if you enter a repo term (or terms) that aren't found throughout your configurations, it will show a warning: ```vcspull-console $ vcspull sync non_existent_repo No repo found in config(s) for "non_existent_repo" ``` ```vcspull-console $ vcspull sync non_existent_repo existing_repo No repo found in config(s) for "non_existent_repo" ``` ```vcspull-console $ vcspull sync non_existent_repo existing_repo another_repo_not_in_config No repo found in config(s) for "non_existent_repo" No repo found in config(s) for "another_repo_not_in_config" ``` Since syncing terms are treated as a filter rather than a lookup, the message is considered a warning, so will not exit even if `--exit-on-error` flag is used. ### Syncing As of 1.13.x, vcspull will continue to the next repo if an error is encountered when syncing multiple repos. To imitate the old behavior, the `--exit-on-error` / `-x` flag: ```console $ vcspull sync --exit-on-error grako django ``` Print traceback for errored repos: ```console $ vcspull --log-level DEBUG sync --exit-on-error grako django ``` --- # vcspull worktree Source: https://vcspull.git-pull.com/cli/worktree/ (cli-worktree)= # vcspull worktree The `vcspull worktree` command manages [git worktrees] declaratively from your vcspull configuration. Instead of manually running `git worktree add` for each branch or tag, you declare the desired worktrees in YAML and let vcspull create, update, and prune them. [git worktrees]: https://git-scm.com/docs/git-worktree ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: worktree :nosubcommands: :nodescription: ``` Choose a subcommand for details: - {ref}`cli-worktree-list` — show configured worktrees and their status - {ref}`cli-worktree-sync` — create or update worktrees - {ref}`cli-worktree-prune` — remove worktrees not in configuration ```{toctree} :maxdepth: 1 :hidden: list sync prune ``` ## Configuration Worktrees are configured as a list under a repository entry. Each worktree requires a `dir` (relative to workspace root or absolute) and exactly one ref type: `tag`, `branch`, or `commit`. ```yaml ~/code/: myproject: url: "git+https://github.com/myorg/myproject.git" worktrees: # Pin a stable release (detached HEAD) - dir: "../myproject-v2.0" tag: "v2.0.0" lock: true lock_reason: "production reference" # Track a development branch (updatable) - dir: "../myproject-dev" branch: "develop" # Pin a specific commit (detached HEAD) - dir: "../myproject-bisect" commit: "abc1234" ``` ### Fields | Field | Required | Description | |-------|----------|-------------| | `dir` | yes | Worktree path (relative to workspace root or absolute) | | `tag` | one of | Tag to checkout (creates detached HEAD) | | `branch` | one of | Branch to checkout (can be updated/pulled) | | `commit` | one of | Commit SHA to checkout (creates detached HEAD) | | `lock` | no | Lock the worktree to prevent accidental removal | | `lock_reason` | no | Reason for locking (implies `lock: true`) | Exactly one of `tag`, `branch`, or `commit` must be specified per entry. ## Integration with vcspull sync The `vcspull sync` command can sync worktrees alongside repositories using the `--include-worktrees` flag: ```console $ vcspull sync --include-worktrees '*' ``` Preview the combined operation: ```console $ vcspull sync --include-worktrees --dry-run '*' ``` This first syncs (clones/updates) the repository itself, then processes its configured worktrees. ## JSON output All subcommands support `--json` for structured output: ```console $ vcspull worktree list --json 'myproject' ``` Each worktree entry emits: ```json { "worktree_path": "~/code/myproject-v2.0", "ref_type": "tag", "ref_value": "v2.0.0", "action": "unchanged", "exists": true, "is_dirty": false, "detail": "tag worktree already exists", "error": null } ``` ### NDJSON output Stream events line-by-line with `--ndjson`: ```console $ vcspull worktree list --ndjson 'myproject' ``` Each line is a self-contained JSON object, suitable for piping to [jq] or log aggregation. [jq]: https://stedolan.github.io/jq/ ## Safety vcspull applies several safety checks before modifying worktrees: **Dirty worktrees are BLOCKED.** If a worktree has uncommitted changes, vcspull will not update or remove it. Commit or stash your changes first. **Missing refs are ERROR.** If a configured tag, branch, or commit doesn't exist in the repository, the entry is marked as an error. Fetch from the remote to make the ref available. **Lock support.** Worktrees created with `lock: true` or `lock_reason` are locked via `git worktree lock`, preventing accidental removal with `git worktree remove`. ## Pattern filtering Filter which repositories are processed using fnmatch-style patterns: ```console $ vcspull worktree list 'django*' ``` ```console $ vcspull worktree sync 'myproject' 'another-repo' ``` Use `'*'` to match all repositories with worktrees configured. ## Workspace filtering Filter by workspace root directory: ```console $ vcspull worktree list --workspace ~/code/ ``` ## Configuration file selection Specify a custom config file: ```console $ vcspull worktree list --file ~/projects/.vcspull.yaml ``` ## Color output Control colored output with `--color`: - `--color auto` (default): Use colors if outputting to a terminal - `--color always`: Always use colors - `--color never`: Never use colors The `NO_COLOR` environment variable is also respected. ## Use cases ### LLM / agentic workflows Clone a repository trunk and pin multiple release tags for reference: ```yaml ~/code/: cpython: url: "git+https://github.com/python/cpython.git" worktrees: - dir: "../cpython-3.12" tag: "v3.12.0" - dir: "../cpython-3.13" tag: "v3.13.0" ``` This gives agents read-only snapshots of specific versions alongside the main checkout. ### Parallel development across branches Work on multiple feature branches without switching: ```yaml ~/code/: webapp: url: "git+https://github.com/myorg/webapp.git" worktrees: - dir: "../webapp-feature-auth" branch: "feature/auth" - dir: "../webapp-feature-billing" branch: "feature/billing" ``` ### Maintaining versioned reference copies Lock stable releases to prevent accidental modification: ```yaml ~/code/: library: url: "git+https://github.com/myorg/library.git" worktrees: - dir: "../library-v1" tag: "v1.0.0" lock: true lock_reason: "legacy API reference" - dir: "../library-v2" tag: "v2.0.0" lock: true lock_reason: "current stable reference" ``` --- # vcspull worktree list Source: https://vcspull.git-pull.com/cli/worktree/list/ (cli-worktree-list)= # vcspull worktree list Show configured worktrees and their planned status without making changes. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: worktree list ``` ## Basic usage List all configured worktrees: ```console $ vcspull worktree list ``` Each worktree is displayed with a status symbol: | Symbol | Action | Meaning | |--------|--------|---------| | `+` | CREATE | Worktree doesn't exist yet, will be created | | `~` | UPDATE | Branch worktree exists, will pull latest | | `✓` | UNCHANGED | Tag/commit worktree exists, already at target | | `⚠` | BLOCKED | Worktree has uncommitted changes | | `✗` | ERROR | Operation failed (ref not found, etc.) | ## Filtering Filter to specific repositories: ```console $ vcspull worktree list 'myproject' ``` Use fnmatch-style patterns: ```console $ vcspull worktree list 'django*' ``` ## JSON output ```console $ vcspull worktree list --json 'myproject' ``` ## NDJSON output ```console $ vcspull worktree list --ndjson 'myproject' ``` --- # vcspull worktree prune Source: https://vcspull.git-pull.com/cli/worktree/prune/ (cli-worktree-prune)= # vcspull worktree prune Remove worktrees that are no longer in your configuration. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: worktree prune ``` ## Basic usage Prune orphaned worktrees: ```console $ vcspull worktree prune '*' ``` Prune scans all git worktrees registered with each matched repository and removes any that don't appear in the current config. Repositories that have had their `worktrees` config removed entirely are also scanned. ## Dry run Preview what would be removed: ```console $ vcspull worktree prune --dry-run '*' ``` ## Filtering Prune worktrees for specific repositories: ```console $ vcspull worktree prune 'myproject' ``` ## JSON output ```console $ vcspull worktree prune --json '*' ``` ## NDJSON output ```console $ vcspull worktree prune --ndjson '*' ``` --- # vcspull worktree sync Source: https://vcspull.git-pull.com/cli/worktree/sync/ (cli-worktree-sync)= # vcspull worktree sync Create or update worktrees to match configuration. ## Command ```{eval-rst} .. argparse:: :module: vcspull.cli :func: create_parser :prog: vcspull :path: worktree sync ``` ## Basic usage Sync all configured worktrees: ```console $ vcspull worktree sync '*' ``` The sync subcommand: - Creates missing worktrees at the configured `dir` - Pulls latest changes for `branch` worktrees - Leaves `tag` and `commit` worktrees unchanged (they're immutable) - Skips worktrees with uncommitted changes (BLOCKED) ## Dry run Preview what would happen without making changes: ```console $ vcspull worktree sync --dry-run '*' ``` ## Filtering Sync worktrees for specific repositories: ```console $ vcspull worktree sync 'myproject' ``` Use fnmatch-style patterns: ```console $ vcspull worktree sync 'django*' ``` ## JSON output ```console $ vcspull worktree sync --json '*' ``` ## NDJSON output ```console $ vcspull worktree sync --ndjson '*' ``` --- # Config generation Source: https://vcspull.git-pull.com/configuration/generation/ (config-generation)= # Config generation The `vcspull import` command can generate configuration by fetching repositories from remote services. See {ref}`cli-import` for details. Supported services: GitHub, GitLab, Codeberg, Gitea, Forgejo, AWS CodeCommit. Example — import all repos from a GitLab group: ```console $ vcspull import gitlab my-group \ --workspace ~/code \ --mode org ``` --- # Configuration Source: https://vcspull.git-pull.com/configuration/ (configuration)= # Configuration ::::{grid} 1 1 2 2 :gutter: 2 2 3 3 :::{grid-item-card} Config Generation :link: generation :link-type: doc Import repos from forges and generate config automatically. ::: :::: ## URL Format Repo type and address is [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986) style URLs. You may recognize this from `pip`'s [VCS URL] format. [vcs url]: https://pip.pypa.io/en/latest/topics/vcs-support/ ## Config locations You can place the file in one of three places: 1. Home: _~/.vcspull.yaml_ 2. [XDG] home directory: `$XDG_CONFIG_HOME/vcspull/` Example: _~/.config/vcspull/myrepos.yaml_ `XDG_CONFIG_HOME` is often _~/.config/vcspull/_, but can vary on platform, to check: ```console $ echo $XDG_CONFIG_HOME ``` 3. Anywhere (and trigger via `vcspull sync -c ./path/to/file.yaml sync [repo_name]`) [xdg]: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html ## Schema ```{warning} This structure is subject to break in upcoming releases. ``` ```yaml ~/workdir/: repo_name: remotes: origin: git_repo_url ``` ### Examples ````{tab} Simple ```{literalinclude} ../../examples/remotes.yaml :language: yaml ``` To pull _kaptan_: ```console $ vcspull sync kaptan ``` ```` ````{tab} Complex **Christmas tree** config showing off every current feature and inline shortcut available. ```{literalinclude} ../../examples/christmas-tree.yaml :language: yaml ``` ```` ````{tab} Open Source Student **Code scholar** This file is used to checkout and sync multiple open source configs. YAML: ```{literalinclude} ../../examples/code-scholar.yaml :language: yaml ``` ```` ## Worktree configuration Repositories can declare worktrees—additional checkouts of specific tags, branches, or commits in separate directories. Worktrees are listed under the `worktrees` key of a repository entry: ```{literalinclude} ../../examples/worktrees.yaml :language: yaml ``` Each worktree entry requires: - `dir`: Path for the worktree (relative to workspace root or absolute) - Exactly one of `tag`, `branch`, or `commit` Optional fields: - `lock`: Lock the worktree to prevent accidental removal - `lock_reason`: Reason for locking (implies `lock: true`) See {ref}`cli-worktree` for full command documentation. (config-pin)= ## Repository pinning Repositories can be **pinned** to prevent automated commands from modifying their configuration entries. This is useful for pinned forks, company mirrors, or any repository whose URL or config shape you manage by hand. Here is a configuration showing all three pin forms side by side: ```yaml ~/code/: # Global pin — blocks ALL operations (import, add, discover, fmt, merge) internal-fork: repo: "git+git@github.com:myorg/internal-fork.git" options: pin: true pin_reason: "pinned to company fork — update manually" # Per-operation pin — only import and fmt are blocked my-framework: repo: "git+git@github.com:myorg/my-framework.git" options: pin: import: true fmt: true pin_reason: "URL managed manually; formatting intentional" # Shorthand — equivalent to pin: {import: true} stable-dep: repo: "git+https://github.com/upstream/stable-dep.git" options: allow_overwrite: false ``` ### Pin all operations Set `pin: true` inside `options` to block every mutation command. This is the simplest form — no automated vcspull command can modify this entry: ```yaml ~/code/: internal-fork: repo: "git+git@github.com:myorg/internal-fork.git" options: pin: true pin_reason: "pinned to company fork — update manually" ``` ### Pin specific operations Pass a mapping instead of a boolean to pin only the operations you care about. Unlisted keys default to `false` (unpinned): ```yaml ~/code/: my-framework: repo: "git+git@github.com:myorg/my-framework.git" options: pin: import: true fmt: true pin_reason: "URL managed manually; formatting intentional" ``` Available pin keys: | Key | Blocks | |------------|-----------------------------------------------------------| | `import` | `vcspull import --sync` from replacing this URL | | `add` | `vcspull add` from overwriting this entry | | `discover` | `vcspull discover` from overwriting this entry | | `fmt` | `vcspull fmt` from normalizing this entry | | `merge` | Duplicate-workspace-root merge from displacing this entry | ### Shorthand: allow_overwrite `allow_overwrite: false` is a convenience shorthand equivalent to `pin: {import: true}`. It only guards against `vcspull import --sync`: ```yaml ~/code/: stable-dep: repo: "git+https://github.com/upstream/stable-dep.git" options: allow_overwrite: false ``` ### Pin behavior - **Defaults** — repositories are unpinned. All operations proceed normally unless a pin is explicitly set. - **Boolean pin** — `pin: true` blocks all five operations (`import`, `add`, `discover`, `fmt`, `merge`). - **Per-operation pin** — only the listed keys are blocked; unlisted keys default to `false` (unpinned). - **pin_reason** — an optional human-readable string shown in log output when an operation is skipped. It is purely informational and does not imply `pin: true` on its own. - **Advisory** — pins prevent automated commands from modifying the entry. You can still edit the configuration file by hand at any time. Each command handles pins differently: | Command | Pin effect | Log level | |---------|------------|-----------| | `vcspull import --sync` | Skips URL replacement | info | | `vcspull add` | Skips with warning | warning | | `vcspull discover` | Silently skips | debug | | `vcspull fmt` | Preserves entry verbatim | (silent) | | Workspace merge | Pinned entry wins conflict | info | ```{note} The `pin` and `pin_reason` fields described here live under `options` and guard the *configuration entry* against mutation by vcspull commands. This is different from the worktree-level `lock` / `lock_reason` that lives inside individual `worktrees` entries and passes `--lock` to `git worktree add`. See {ref}`cli-worktree` for worktree locking. ``` ## Import provenance When repositories are imported with `--sync` or `--prune`, vcspull records which service and owner the import came from. This is stored in a `metadata.imported_from` field: ```yaml ~/code/: my-project: repo: "git+git@github.com:myorg/my-project.git" metadata: imported_from: "github:myorg" ``` The `metadata` block is managed by vcspull — you generally don't need to edit it by hand. It is used to scope pruning: when re-importing with `--sync`, only entries tagged with the matching source are candidates for removal. See {ref}`cli-import` for full details on `--sync` and `--prune`. ## Caveats (git-remote-ssh-git)= ### SSH Git URLs For git remotes using SSH authorization such as `git+git@github.com:tony/kaptan.git` use `git+ssh`: ```console git+ssh://git@github.com/tony/kaptan.git ``` ```{toctree} :hidden: generation ``` --- # Changelog Source: https://vcspull.git-pull.com/history/ (changes)= (changelog)= (history)= ```{currentmodule} libtmux ``` ```{include} ../CHANGES ``` --- # vcspull Source: https://vcspull.git-pull.com/ --- hide-toc: true --- (index)= # vcspull CLI workspace manager for VCS repos. Sync, organize, and manage multiple git (and hg/svn) repositories from a single YAML config. ::::{grid} 1 2 3 3 :gutter: 2 2 3 3 :::{grid-item-card} Quickstart :link: quickstart :link-type: doc Install and sync your first repos. ::: :::{grid-item-card} CLI Reference :link: cli/index :link-type: doc Every command, flag, and option. ::: :::{grid-item-card} Configuration :link: configuration/index :link-type: doc Config format, locations, schema, and examples. ::: :::: ::::{grid} 1 1 2 2 :gutter: 2 2 3 3 :::{grid-item-card} Internals :link: internals/index :link-type: doc Internal Python API for contributors. ::: :::{grid-item-card} Contributing :link: project/index :link-type: doc Development setup, code style, and release process. ::: :::: ## Install ```console $ pip install vcspull ``` ```console $ uv tool install vcspull ``` ```console $ pipx install vcspull ``` See [Quickstart](quickstart.md) for all installation methods and first steps. ## At a glance ```yaml ~/code/: flask: "git+https://github.com/pallets/flask.git" django: "git+https://github.com/django/django.git" ~/study/: linux: "git+https://github.com/torvalds/linux.git" ``` ```console $ vcspull sync ``` ```{image} _static/vcspull-demo.gif :width: 100% :loading: lazy ``` ```{toctree} :hidden: quickstart cli/index configuration/index api/index internals/index project/index history migration GitHub ``` --- # vcspull add - vcspull.cli.add Source: https://vcspull.git-pull.com/internals/api/cli/add/ # vcspull add - `vcspull.cli.add` ```{eval-rst} .. automodule:: vcspull.cli.add :members: :show-inheritance: :undoc-members: ``` --- # vcspull discover - vcspull.cli.discover Source: https://vcspull.git-pull.com/internals/api/cli/discover/ # vcspull discover - `vcspull.cli.discover` ```{eval-rst} .. automodule:: vcspull.cli.discover :members: :show-inheritance: :undoc-members: ``` --- # vcspull fmt - vcspull.cli.fmt Source: https://vcspull.git-pull.com/internals/api/cli/fmt/ # vcspull fmt - `vcspull.cli.fmt` ```{eval-rst} .. automodule:: vcspull.cli.fmt :members: :show-inheritance: :undoc-members: ``` --- # vcspull import - vcspull.cli.import_cmd Source: https://vcspull.git-pull.com/internals/api/cli/import/ # vcspull import - `vcspull.cli.import_cmd` ```{eval-rst} .. automodule:: vcspull.cli.import_cmd :members: :show-inheritance: :undoc-members: ``` ```{eval-rst} .. automodule:: vcspull.cli.import_cmd._common :members: :show-inheritance: :undoc-members: ``` --- # CLI Source: https://vcspull.git-pull.com/internals/api/cli/ (api_cli)= (api_commands)= # CLI ```{toctree} :caption: General commands :maxdepth: 1 sync add import discover list search status worktree fmt ``` ## vcspull CLI - `vcspull.cli` ```{eval-rst} .. automodule:: vcspull.cli :members: :show-inheritance: :undoc-members: ``` --- # vcspull list - vcspull.cli.list Source: https://vcspull.git-pull.com/internals/api/cli/list/ # vcspull list - `vcspull.cli.list` ```{eval-rst} .. automodule:: vcspull.cli.list :members: :show-inheritance: :undoc-members: ``` --- # vcspull search - vcspull.cli.search Source: https://vcspull.git-pull.com/internals/api/cli/search/ # vcspull search - `vcspull.cli.search` .. automodule:: vcspull.cli.search --- # vcspull status - vcspull.cli.status Source: https://vcspull.git-pull.com/internals/api/cli/status/ # vcspull status - `vcspull.cli.status` ```{eval-rst} .. automodule:: vcspull.cli.status :members: :show-inheritance: :undoc-members: ``` --- # vcspull sync - vcspull.cli.sync Source: https://vcspull.git-pull.com/internals/api/cli/sync/ # vcspull sync - `vcspull.cli.sync` ```{eval-rst} .. automodule:: vcspull.cli.sync :members: :show-inheritance: :undoc-members: ``` --- # vcspull worktree - vcspull.cli.worktree Source: https://vcspull.git-pull.com/internals/api/cli/worktree/ # vcspull worktree - `vcspull.cli.worktree` ```{eval-rst} .. automodule:: vcspull.cli.worktree :members: :show-inheritance: :undoc-members: ``` --- # Config reader - vcspull._internal.config_reader Source: https://vcspull.git-pull.com/internals/api/config_reader/ # Config reader - `vcspull._internal.config_reader` :::{warning} Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions! If you need an internal API stabilized please [file an issue](https://github.com/vcs-python/vcspull/issues). ::: ```{eval-rst} .. automodule:: vcspull._internal.config_reader :members: :show-inheritance: :undoc-members: ``` --- # Internal Python API Source: https://vcspull.git-pull.com/internals/api/ (internals-api)= # Internal Python API :::{warning} Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions! If you need an internal API stabilized please [file an issue](https://github.com/vcs-python/vcspull/issues). ::: ```{toctree} config_reader worktree_sync private_path ``` --- # PrivatePath – vcspull._internal.private_path Source: https://vcspull.git-pull.com/internals/api/private_path/ # PrivatePath – `vcspull._internal.private_path` :::{warning} `PrivatePath` is an internal helper. Its import path and behavior may change without notice. File an issue if you rely on it downstream so we can discuss a supported API. ::: `PrivatePath` subclasses `pathlib.Path` and normalizes every textual rendering (`str()`/`repr()`) so the current user’s home directory is collapsed to `~`. The class behaves exactly like the standard path object for filesystem ops; it only alters how the path is displayed. This keeps CLI logs, JSON/NDJSON output, and tests from leaking usernames while preserving full absolute paths for internal logic. ```python from vcspull._internal.private_path import PrivatePath home_repo = PrivatePath("~/code/vcspull") print(home_repo) # -> ~/code/vcspull print(repr(home_repo)) # -> "PrivatePath('~/code/vcspull')" ``` ## Usage guidelines - Wrap any path destined for user-facing output (logs, console tables, JSON payloads) in `PrivatePath` before calling `str()`. - The helper is safe to instantiate with `pathlib.Path` objects or strings; it does not touch relative paths that lack a home prefix. - Prefer storing raw `pathlib.Path` objects (or strings) in configuration models, then convert to `PrivatePath` at the presentation layer. This keeps serialization and equality checks deterministic while still masking the home directory when needed. ## Why not `contract_user_home`? The previous `contract_user_home()` helper duplicated the tilde-collapsing logic in multiple modules and required callers to remember to run it themselves. By centralizing the behavior in a `pathlib.Path` subclass we get: - Built-in protection—`str()` and `repr()` automatically apply the privacy filter. - Consistent behavior across every CLI command and test fixture. - Easier mocking in tests, because `PrivatePath` respects monkeypatched `Path.home()` implementations. If you need alternative redaction behavior, consider composing your own helper around `PrivatePath` instead of reintroducing ad hoc string munging. ```{eval-rst} .. automodule:: vcspull._internal.private_path :members: :show-inheritance: :undoc-members: ``` --- # Worktree sync - vcspull._internal.worktree_sync Source: https://vcspull.git-pull.com/internals/api/worktree_sync/ # Worktree sync - `vcspull._internal.worktree_sync` :::{warning} Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions! If you need an internal API stabilized please [file an issue](https://github.com/vcs-python/vcspull/issues). ::: ```{eval-rst} .. automodule:: vcspull._internal.worktree_sync :members: :show-inheritance: :undoc-members: ``` --- # Internals Source: https://vcspull.git-pull.com/internals/ (internals)= # Internals ```{warning} Everything in this section is **internal implementation detail**. There is no stability guarantee. Interfaces may change or be removed without notice between any release. If you are building workflows with vcspull, use the [CLI](../cli/index.md) or refer to the [libvcs API](https://libvcs.git-pull.com/). ``` ::::{grid} 1 1 2 2 :gutter: 2 2 3 3 :::{grid-item-card} Python API :link: api/index :link-type: doc Internal module reference for contributors and plugin authors. ::: :::{grid-item-card} CLI Internals :link: api/cli/index :link-type: doc Python surface behind the CLI commands. ::: :::: ```{toctree} :hidden: api/index api/cli/index ``` --- # Migration notes Source: https://vcspull.git-pull.com/migration/ (migration)= ```{currentmodule} libtmux ``` ```{include} ../MIGRATION ``` --- # Code Style Source: https://vcspull.git-pull.com/project/code-style/ (code-style)= # Code Style ## Formatting and linting vcspull uses [ruff](https://docs.astral.sh/ruff/) for formatting and linting. ```console $ uv run ruff format . ``` ```console $ uv run ruff check . --fix --show-fixes ``` ## Type checking [mypy](https://mypy-lang.org/) runs in strict mode. ```console $ uv run mypy ``` ## Docstrings Follow [NumPy docstring convention](https://numpydoc.readthedocs.io/en/latest/format.html). ## Imports - Use `from __future__ import annotations` in every file. - Prefer namespace imports for stdlib: `import pathlib` not `from pathlib import Path`. - Use `import typing as t` and access via `t.NamedTuple`, `t.TYPE_CHECKING`, etc. --- # Development Source: https://vcspull.git-pull.com/project/contributing/ # Development Developing python projects associated with [git-pull.com] all use the same structure and workflow. At a later point these will refer to that website for documentation. [git-pull.com]: https://git-pull.com ## Bootstrap the project Install and [git] and [uv] Clone: ```console $ git clone https://github.com/vcs-python/vcspull.git ``` ```console $ cd vcspull ``` Install packages: ```console $ uv sync --all-extras --dev ``` [installation documentation]: https://docs.astral.sh/uv/getting-started/installation/ [git]: https://git-scm.com/ ## Development loop ### Tests [pytest] is used for tests. [pytest]: https://pytest.org/ #### Rerun on file change via [pytest-watcher] (works out of the box): ```console $ just start ``` via [entr(1)] (requires installation): ```console $ just watch-test ``` [pytest-watcher]: https://github.com/olzhasar/pytest-watcher #### Manual (just the command, please) ```console $ uv run py.test ``` or: ```console $ just test ``` #### pytest options `PYTEST_ADDOPTS` can be set in the commands below. For more information read [docs.pytest.com] for the latest documentation. [docs.pytest.com]: https://docs.pytest.org/ Verbose: ```console $ env PYTEST_ADDOPTS="-verbose" just start ``` Drop into `pdb` on first error: ```console $ env PYTEST_ADDOPTS="-x -s --pdb" just start ``` If you have [ipython] installed: ```console $ env PYTEST_ADDOPTS="--pdbcls=IPython.terminal.debugger:TerminalPdb" \ just start ``` [ipython]: https://ipython.org/ ### Documentation [sphinx] is used for documentation generation. In the future this may change to [docusaurus]. Default preview server: http://localhost:8022 [sphinx]: https://www.sphinx-doc.org/ [docusaurus]: https://docusaurus.io/ #### Rerun on file change [sphinx-autobuild] will automatically build the docs, it also handles launching a server, rebuilding file changes, and updating content in the browser: ```console $ cd docs ``` ```console $ just start ``` If doing css adjustments: ```console $ just design ``` [sphinx-autobuild]: https://github.com/executablebooks/sphinx-autobuild Rebuild docs on file change (requires [entr(1)]): ```console $ cd docs ``` ```console $ just dev ``` Use two terminals if needed: ```console $ just watch ``` ```console $ just serve ``` #### Manual (just the command, please) ```console $ cd docs ``` Build: ```console $ just html ``` Launch server: ```console $ just serve ``` ## Linting ### ruff The project uses [ruff] to handle formatting, sorting imports and linting. ````{tab} Command uv: ```console $ uv run ruff check . ``` If you setup manually: ```console $ ruff check . ``` ```` ````{tab} just ```console $ just ruff ``` ```` ````{tab} Watch ```console $ just watch-ruff ``` requires [`entr(1)`]. ```` ````{tab} Fix files uv: ```console $ uv run ruff check . --fix ``` If you setup manually: ```console $ ruff check . --fix ``` ```` #### ruff format [ruff format] is used for formatting. ````{tab} Command uv: ```console $ uv run ruff format . ``` If you setup manually: ```console $ ruff format . ``` ```` ````{tab} just ```console $ just ruff-format ``` ```` ### mypy [mypy] is used for static type checking. ````{tab} Command uv: ```console $ uv run mypy . ``` If you setup manually: ```console $ mypy . ``` ```` ````{tab} just ```console $ just mypy ``` ```` ````{tab} Watch ```console $ just watch-mypy ``` requires [`entr(1)`]. ```` ````{tab} Configuration See `[tool.mypy]` in pyproject.toml. ```{literalinclude} ../pyproject.toml :language: toml :start-at: "[tool.mypy]" :end-before: "[tool" ``` ```` ## Publishing to PyPI [uv] handles virtualenv creation, package requirements, versioning, building, and publishing. Therefore there is no setup.py or requirements files. Update `__version__` in `__about__.py` and `pyproject.toml`:: git commit -m 'build(vcspull): Tag v0.1.1' git tag v0.1.1 git push git push --tags GitHub Actions will detect the new git tag, and in its own workflow run `uv build` and push to PyPI. [uv]: https://github.com/astral-sh/uv [entr(1)]: http://eradman.com/entrproject/ [`entr(1)`]: http://eradman.com/entrproject/ [ruff format]: https://docs.astral.sh/ruff/formatter/ [ruff]: https://ruff.rs [mypy]: http://mypy-lang.org/ --- # Project Source: https://vcspull.git-pull.com/project/ (project)= # Project Information for contributors and maintainers. ::::{grid} 1 1 2 2 :gutter: 2 2 3 3 :::{grid-item-card} Contributing :link: contributing :link-type: doc Development setup, running tests, submitting PRs. ::: :::{grid-item-card} Code Style :link: code-style :link-type: doc Ruff, mypy, NumPy docstrings, import conventions. ::: :::{grid-item-card} Releasing :link: releasing :link-type: doc Release checklist and version policy. ::: :::: ```{toctree} :hidden: contributing code-style releasing ``` --- # Releasing Source: https://vcspull.git-pull.com/project/releasing/ (releasing)= # Releasing ## Version policy vcspull follows [semantic versioning](https://semver.org/). Internal APIs (everything under `_internal/`) carry no stability guarantee. ## Release checklist 1. Update `CHANGES` with the new version and date. 2. Bump the version in `src/vcspull/__about__.py`. 3. Create a signed tag: `git tag -s v`. 4. Push the tag: `git push --tags`. 5. Publish to PyPI: `uv build && uv publish`. --- # Quickstart Source: https://vcspull.git-pull.com/quickstart/ (quickstart)= # Quickstart ## Installation For latest official version: ```console $ pip install --user vcspull ``` Or using uv: ```console $ uv tool install vcspull ``` For one-time use without installation: ```console $ uvx vcspull ``` Upgrading: ```console $ pip install --user --upgrade vcspull ``` Or with uv: ```console $ uv tool upgrade vcspull ``` (developmental-releases)= ### Developmental releases New versions of vcspull are published to PyPI as alpha, beta, or release candidates. In their versions you will see notification like `a1`, `b1`, and `rc1`, respectively. `1.10.0b4` would mean the 4th beta release of `1.10.0` before general availability. - [pip]\: ```console $ pip install --user --upgrade --pre vcspull ``` - [pipx]\: ```console $ pipx install --suffix=@next 'vcspull' --pip-args '\--pre' --force // Usage: vcspull@next sync [config] ``` - [uv tool install][uv-tools]\: ```console $ uv tool install --prerelease allow vcspull ``` - [uv]\: ```console $ uv add vcspull --prerelease allow ``` - [uvx]\: ```console $ uvx --from 'vcspull' --prerelease allow vcspull ``` via trunk (can break easily): - [pip]\: ```console $ pip install --user -e git+https://github.com/vcs-python/vcspull.git#egg=vcspull ``` - [pipx]\: ```console $ pipx install --suffix=@master 'vcspull @ git+https://github.com/vcs-python/vcspull.git@master' --force ``` - [uv]\: ```console $ uv tool install vcspull --from git+https://github.com/vcs-python/vcspull.git ``` [pip]: https://pip.pypa.io/en/stable/ [pipx]: https://pypa.github.io/pipx/docs/ [uv]: https://docs.astral.sh/uv/ [uv-tools]: https://docs.astral.sh/uv/concepts/tools/ [uvx]: https://docs.astral.sh/uv/guides/tools/ ## Configuration ```{seealso} {ref}`configuration` and {ref}`cli-import`. ``` We will check out the source code of [flask][flask] to `~/code/flask`. Prefer JSON? Create a `~/.vcspull.json` file: ```json { "~/code/": { "flask": "git+https://github.com/mitsuhiko/flask.git" } } ``` YAML? Create a `~/.vcspull.yaml` file: ```yaml ~/code/: "flask": "git+https://github.com/mitsuhiko/flask.git" ``` Already have repositories cloned locally? Use `vcspull discover ~/code --recursive` to detect existing Git checkouts and append them to your configuration. See {ref}`cli-discover` for more details and options such as `--workspace-root` and `--yes` for unattended runs. After editing or discovering repositories, run `vcspull fmt --write` (documented in {ref}`cli-fmt`) to normalize keys and keep your configuration tidy. The `git+` in front of the repository URL. Mercurial repositories use `hg+` and Subversion will use `svn+`. Repo type and address is specified in [pip vcs url][pip vcs url] format. Now run the command, to pull all the repositories in your `.vcspull.yaml` / `.vcspull.json`. ```console $ vcspull sync --all ``` Want to manage multiple branches or tags of the same repository? See {ref}`cli-worktree` for declarative worktree support. Also, you can sync arbitrary projects, lets assume you have a mercurial repo but need a git dependency, in your project add `.deps.yaml` (can be any name): ```yaml ./vendor/: sdl2pp: "git+https://github.com/libSDL2pp/libSDL2pp.git" ``` Use `-f/--file` to specify a config. ```console $ vcspull sync --file .deps.yaml --all ``` You can also use [fnmatch] to pull repositories from your config in various fashions, e.g.: ```console $ vcspull sync django ``` ```console $ vcspull sync django\* ``` ```console $ vcspull sync "django*" ``` Filter by VCS URL: Any repo beginning with `http`, `https` or `git` will be look up repos by the vcs url. Pull / update repositories you have with github in the repo url: ```console $ vcspull sync "git+https://github.com/yourusername/*" ``` Pull / update repositories you have with bitbucket in the repo url: ```console $ vcspull sync "git+https://*bitbucket*" ``` Filter by the path of the repo on your local machine: Any repo beginning with `/`, `./`, `~` or `$HOME` will scan for patterns of where the project is on your system: Pull all repos inside of _~/study/python_: ```console $ vcspull sync "$HOME/study/python" ``` Pull all the repos you have in directories in my config with "python": ```console $ vcspull sync ~/*python* ``` [pip vcs url]: http://www.pip-installer.org/en/latest/logic.html#vcs-support [flask]: http://flask.pocoo.org/ [fnmatch]: http://pubs.opengroup.org/onlinepubs/009695399/functions/fnmatch.html ---