Skip to content

cli(add): accept a repository URL, not only a checkout path#563

Open
tony wants to merge 3 commits into
masterfrom
vcspull-add-local
Open

cli(add): accept a repository URL, not only a checkout path#563
tony wants to merge 3 commits into
masterfrom
vcspull-add-local

Conversation

@tony

@tony tony commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

  • Add URL-first declaring to vcspull add: pass a repository URL and it records the config entry without cloning, leaving the checkout to vcspull sync. Previously add required an existing directory on disk, so declaring a not-yet-cloned repo meant hand-editing YAML.
  • Reuse libvcs GitURL.is_valid to discriminate path from URL, with an existing directory always winning — every current path-mode invocation behaves exactly as before.
  • Infer the workspace from the roots the config already declares when a URL is given with no --workspace, since there is no parent directory to derive one from. The choice folds into the existing confirm prompt rather than adding a second one.

Changes

  • src/vcspull/cli/add.py: Discriminate the repo_path argument as path-or-URL in handle_add_command. Derive the repo name from the URL basename (overridable with --name), reject a URL argument combined with --url, and offer declared workspace roots in the confirm prompt. Extract config-file resolution into _resolve_config_file / ConfigFileResolution so the prompt can read declared roots before the write path runs; add _repo_name_from_url and _declared_workspace_labels helpers.
  • tests/cli/test_add.py: Parameterized URL-mode coverage — URL forms (https, scp, git+), first-root default and numeric selection, decline and out-of-range paths, --workspace override, no-declared-roots fallback to cwd, name override, the redundant---url error, and path-wins precedence on an argument that is simultaneously a valid scp URL and a legal directory name.
  • docs/cli/add.md: Reframe the intro around both forms, add a "Declaring a repository you have not cloned" section, and restore the near-1:1 migration diff from the old vcspull import <name> <url>.
  • CHANGES: v1.66 "What's new" entry.

Design decisions

  • Positional polymorphism over a flag or a new subcommand: one argument accepting either form reads naturally, adds no flags, and needs no migration. --url keeps its single meaning as a remote override for path mode. A vcspull declare subcommand would reopen the naming churn that import already went through.
  • Existing directory wins: resolution checks the filesystem before GitURL.is_valid, so a path that also parses as a URL stays a path. This is the compatibility guarantee — no current invocation changes behavior.
  • Declare-only, no --clone flag: vcspull sync already clones with progress, parallelism, and per-VCS handling. A clone flag would duplicate a better implementation and make add the first config command to mutate a working tree.
  • Single prompt: the workspace choice is presented inline in the confirm step, so URL mode still asks once. Non-interactive runs (-y, --dry-run, EOF) take the default root silently, consistent with how -y already auto-confirms.

Test plan

  • uv run pytest tests/cli/test_add.py — new URL-mode cases plus unchanged path-mode snapshots
  • uv run pytest — full suite green
  • uv run ruff format --check . and uv run ruff check . — clean
  • uv run mypy — clean
  • just build-docs — build succeeded, no new warnings

tony added 3 commits July 21, 2026 18:11
why: Declaring a repository that is not checked out yet required
hand-editing the YAML. `vcspull add` gated on the path existing, and
`--url` only overrode the remote detected from a checkout, so there was
no CLI route from "I know the URL" to a config entry. URL-first adding
existed as `vcspull import <name> <url>` through v1.39 and was dropped
when `add` became path-first; `import` now belongs to the forge
importers, so the capability comes back on `add` instead.

what:
- Accept either a filesystem path or a repository URL as the `add`
  argument, discriminated with libvcs `GitURL.is_valid`
- Resolve an existing directory before considering a URL, so every
  path-mode invocation keeps its current behavior
- Derive the repo name from the URL basename, still overridable with
  `--name`; reject a URL argument combined with `--url`
- Offer workspace roots already declared in the config when a URL has
  no `--workspace`, defaulting to the first and falling back to the
  current directory; fold the choice into the existing confirm prompt
- Extract config-file resolution into `_resolve_config_file` so the
  prompt can read declared roots before the write path runs
- Cover URL forms, root selection, decline paths, and path-wins
  precedence in tests/cli/test_add.py
why: The page framed `add` as pointing vcspull at a checkout on disk,
which is now only half of what the command accepts, and the migration
diff still routed the old `vcspull import <name> <url>` through a path
plus `--url`.

what:
- Reframe the intro around both forms and say the URL form records the
  entry without cloning
- Add a section covering name derivation from the URL, the workspace
  roots offered when `--workspace` is absent, and the rule that an
  existing directory wins over a URL-shaped argument
- Note that `--url` accompanies a path and is rejected alongside a URL
- Restore the near-1:1 migration diff from the old import command
why: Record the new `vcspull add <url>` surface for the v1.66 entry.
@tony

tony commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Code review

Found 2 issues:

  1. _resolve_config_file has a NumPy docstring but no doctest, while its two sibling helpers added in the same change (_repo_name_from_url, _declared_workspace_labels) both have working doctests. AGENTS.md says "All functions and methods MUST have working doctests." The explicit-path branch is pure and doctestable with the tmp_path fixture.

def _resolve_config_file(config_file_path_str: str | None) -> ConfigFileResolution:
"""Resolve which config file ``add`` should write to.
Reports discovery outcomes rather than logging them, so callers that only
need the path (to read declared workspace roots, say) do not emit the
discovery messages a second time.
Parameters
----------
config_file_path_str : str | None
Value of ``-f/--file``, or ``None`` to discover a default.
Returns
-------
ConfigFileResolution
``path`` is ``None`` only when ``ambiguous`` is ``True``.
"""
if config_file_path_str:

  1. The #### Add a repository by URL changelog heading is missing its PR ref; it should read #### Add a repository by URL (#563). AGENTS.md says "PR refs (#NN) sit in each deliverable's #### heading," and the shipped v1.65 deliverables follow it (e.g. #### Clearer branch and revision sync failures (#557)).

vcspull/CHANGES

Lines 41 to 47 in a5754e6

### What's new
#### Add a repository by URL
{ref}`cli-add` now accepts a repository URL in place of a path, so you can
record a repository you have not cloned yet. The entry lands in your
configuration and {ref}`vcspull sync <cli-sync>` clones it on the next run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant