diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 000000000..642586c4c --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,55 @@ +(api)= + +(reference)= + +# API Reference + +libvcs exposes three public subsystems -- URL parsing, command execution, +and repository synchronization -- plus a pytest plugin for test fixtures. + +All APIs are pre-1.0 and may change between minor versions. +Pin to a range: `libvcs>=0.39,<0.40`. + +## Subsystems + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} URL Parsing +:link: /url/index +:link-type: doc +Detect, validate, and normalize Git / Hg / SVN URLs. +Typed dataclasses with pip- and npm-style support. +::: + +:::{grid-item-card} Commands +:link: /cmd/index +:link-type: doc +Thin Python wrappers around `git`, `hg`, and `svn` CLIs. +Fine-grained control over individual VCS operations. +::: + +:::{grid-item-card} Sync +:link: /sync/index +:link-type: doc +High-level clone-and-update for local repositories. +One call to fetch or create a working copy. +::: + +:::{grid-item-card} pytest Plugin +:link: api/pytest-plugin +:link-type: doc +Session-scoped fixtures for Git, SVN, and Mercurial +repositories. Drop-in test isolation. +::: + +:::: + +```{toctree} +:hidden: + +/url/index +/cmd/index +/sync/index +pytest-plugin +``` diff --git a/docs/pytest-plugin.md b/docs/api/pytest-plugin.md similarity index 100% rename from docs/pytest-plugin.md rename to docs/api/pytest-plugin.md diff --git a/docs/cmd/index.md b/docs/cmd/index.md index 245dca168..686e868b0 100644 --- a/docs/cmd/index.md +++ b/docs/cmd/index.md @@ -28,8 +28,33 @@ The `libvcs.cmd` module provides Python wrappers for VCS command-line tools: | `libvcs.cmd` | Fine-grained control over individual VCS commands | | `libvcs.sync` | High-level repository cloning and updating | +## Modules + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Git +:link: git/index +:link-type: doc +Full git CLI wrapper with sub-command managers (branch, remote, stash, ...). +::: + +:::{grid-item-card} Mercurial +:link: hg +:link-type: doc +Mercurial CLI wrapper. +::: + +:::{grid-item-card} Subversion +:link: svn +:link-type: doc +Subversion CLI wrapper. +::: + +:::: + ```{toctree} -:caption: API +:hidden: git/index hg diff --git a/docs/conf.py b/docs/conf.py index 5af57aa0c..2339bdb36 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,8 +41,10 @@ "sphinxext.opengraph", "sphinxext.rediraffe", "myst_parser", + "sphinx_design", "linkify_issues", ] +myst_heading_anchors = 4 myst_enable_extensions = [ "colon_fence", "substitution", diff --git a/docs/contributing/index.md b/docs/contributing/index.md deleted file mode 100644 index 0bf04d5ea..000000000 --- a/docs/contributing/index.md +++ /dev/null @@ -1,12 +0,0 @@ -(contributing)= - -(developing)= - -# Contributing - -As an open source project, libvcs accepts contributions through GitHub. Below you will find -resources on the internals of the project. - -```{toctree} -workflow -``` diff --git a/docs/index.md b/docs/index.md index 60391c568..fc19563ec 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,30 +2,109 @@ # libvcs -```{include} ../README.md -:start-after: +Typed Python utilities for Git, SVN, and Mercurial. Parse URLs, +execute commands, and synchronize repositories -- all with a +consistent, type-friendly API. + +::::{grid} 1 2 3 3 +:gutter: 2 2 3 3 + +:::{grid-item-card} Quickstart +:link: quickstart +:link-type: doc +Install and parse your first VCS URL in 5 minutes. +::: + +:::{grid-item-card} URL Parsing +:link: url/index +:link-type: doc +Parse, validate, and normalize git/hg/svn URLs. +::: + +:::{grid-item-card} Commands +:link: cmd/index +:link-type: doc +Typed wrappers for git, hg, and svn CLI operations. +::: + +:::{grid-item-card} Sync +:link: sync/index +:link-type: doc +Clone and update local repositories. +::: + +:::{grid-item-card} pytest Plugin +:link: api/pytest-plugin +:link-type: doc +Fixtures for isolated VCS test repos. +::: + +:::{grid-item-card} Project +:link: project/index +:link-type: doc +Contributing, code style, release process. +::: + +:::: + +## Install + +```console +$ pip install libvcs ``` -```{toctree} -:maxdepth: 2 -:hidden: +```console +$ uv add libvcs +``` -quickstart -topics/index -url/index -cmd/index -sync/index -pytest-plugin +```{tip} +libvcs is pre-1.0. Pin to a range: `libvcs>=0.39,<0.40` +``` + +See [Quickstart](quickstart.md) for all methods and first steps. + +## At a glance + +```python +from libvcs.url.git import GitURL + +url = GitURL(url="git@github.com:vcs-python/libvcs.git") +url.hostname # 'github.com' +url.path # 'vcs-python/libvcs' + +GitURL.is_valid(url="https://github.com/vcs-python/libvcs.git") +# True +``` + +libvcs gives you typed dataclasses for every parsed URL, thin CLI +wrappers for `git` / `hg` / `svn`, and high-level sync that clones or +updates a local checkout in one call. + +| Layer | Module | Purpose | +|-------|--------|---------| +| URL parsing | {mod}`libvcs.url` | Detect, validate, normalize VCS URLs | +| Commands | {mod}`libvcs.cmd` | Execute individual VCS CLI operations | +| Sync | {mod}`libvcs.sync` | Clone and update local repositories | + +## Testing + +libvcs ships a [pytest plugin](api/pytest-plugin.md) with +session-scoped fixtures for Git, SVN, and Mercurial repositories: + +```python +def test_my_tool(create_git_remote_repo): + repo_path = create_git_remote_repo() + assert repo_path.exists() ``` ```{toctree} -:caption: Project :hidden: -contributing/index +quickstart +topics/index +api/index internals/index +project/index history migration -GitHub - ``` diff --git a/docs/internals/index.md b/docs/internals/index.md index fb6159812..199caa7fc 100644 --- a/docs/internals/index.md +++ b/docs/internals/index.md @@ -8,7 +8,56 @@ Be careful with these! Internal APIs are **not** covered by version policies. Th If you need an internal API stabilized please [file an issue](https://github.com/vcs-python/libvcs/issues). ::: +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Exceptions +:link: exc +:link-type: doc +Error hierarchy for VCS operations. +::: + +:::{grid-item-card} Types +:link: types +:link-type: doc +Shared type aliases and protocols. +::: + +:::{grid-item-card} Dataclasses +:link: dataclasses +:link-type: doc +Internal dataclass utilities. +::: + +:::{grid-item-card} QueryList +:link: query_list +:link-type: doc +Filterable list for object collections. +::: + +:::{grid-item-card} Run +:link: run +:link-type: doc +Runtime helpers and environment utilities. +::: + +:::{grid-item-card} Subprocess +:link: subprocess +:link-type: doc +Subprocess wrappers for VCS binaries. +::: + +:::{grid-item-card} Shortcuts +:link: shortcuts +:link-type: doc +Convenience functions for common operations. +::: + +:::: + ```{toctree} +:hidden: + exc types dataclasses diff --git a/docs/project/code-style.md b/docs/project/code-style.md new file mode 100644 index 000000000..bfeefe50f --- /dev/null +++ b/docs/project/code-style.md @@ -0,0 +1,54 @@ +(code-style)= + +# Code Style + +## Formatting and linting + +libvcs uses [ruff](https://ruff.rs) for formatting **and** linting in a +single tool. The full rule set is declared in `pyproject.toml` under +`[tool.ruff]`. + +```console +$ uv run ruff format . +``` + +```console +$ uv run ruff check . --fix --show-fixes +``` + +## Type checking + +[mypy](http://mypy-lang.org/) runs in strict mode: + +```console +$ uv run mypy src tests +``` + +## Docstrings + +All public APIs use **NumPy-style** docstrings: + +```python +def fetch(url: str, *, branch: str | None = None) -> str: + """Fetch a remote branch. + + Parameters + ---------- + url : str + Repository URL. + branch : str or None + Branch name. ``None`` means the default branch. + + Returns + ------- + str + The fetched commit hash. + """ +``` + +## Imports + +- `from __future__ import annotations` at the top of every file. +- Standard-library modules use **namespace imports**: `import pathlib`, + not `from pathlib import Path`. +- Typing: `import typing as t`, then `t.Optional`, `t.Any`, etc. diff --git a/docs/project/contributing.md b/docs/project/contributing.md new file mode 100644 index 000000000..b88487b9d --- /dev/null +++ b/docs/project/contributing.md @@ -0,0 +1,10 @@ +(contributing)= + +(developing)= + +# Contributing + +As an open source project, libvcs accepts contributions through GitHub. + +Ready to dive in? See the [Development Workflow](workflow.md) for +environment setup, running tests, linting, and building docs. diff --git a/docs/project/index.md b/docs/project/index.md new file mode 100644 index 000000000..915e334f4 --- /dev/null +++ b/docs/project/index.md @@ -0,0 +1,43 @@ +(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} Development Workflow +:link: workflow +:link-type: doc +Tests, documentation builds, formatting, and linting. +::: + +:::{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 +workflow +code-style +releasing +``` diff --git a/docs/project/releasing.md b/docs/project/releasing.md new file mode 100644 index 000000000..4c39e0182 --- /dev/null +++ b/docs/project/releasing.md @@ -0,0 +1,43 @@ +(releasing)= + +# Releasing + +## Version policy + +libvcs is pre-1.0. Any minor bump (e.g. 0.39 to 0.40) **may** contain +breaking changes. Patch bumps (0.39.0 to 0.39.1) are reserved for +bug-fixes and documentation. + +## Checklist + +1. Ensure `CHANGES` lists every merged PR since the last tag. Credit + contributors by GitHub handle. + +2. Update the version in `src/libvcs/__about__.py` **and** + `pyproject.toml`. + +3. Commit and tag: + + ```console + $ git commit -m 'Tag v0.39.1' + ``` + + ```console + $ git tag v0.39.1 + ``` + +4. Push the commit and tag -- CI will publish to PyPI automatically: + + ```console + $ git push && git push --tags + ``` + +## Manual publish (fallback) + +```console +$ uv build +``` + +```console +$ uv publish +``` diff --git a/docs/contributing/workflow.md b/docs/project/workflow.md similarity index 100% rename from docs/contributing/workflow.md rename to docs/project/workflow.md diff --git a/docs/redirects.txt b/docs/redirects.txt index 655d33a59..3a0e5b777 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -1,9 +1,9 @@ -"api.md" "projects/index.md" +# "api.md" "projects/index.md" # dirhtml: collides with api/index.md "api/base.md" "projects/base.md" "api/git.md" "projects/git.md" "api/hg.md" "projects/hg.md" "api/svn.md" "projects/svn.md" -"developing.md" "contributing/index.md" +"developing.md" "project/contributing.md" # "contributing/internals.md" "contributing/internals/index.md" # dirhtml: same output path "contributing/internals/exc.md" "internals/exc.md" "contributing/internals/index.md" "internals/index.md" @@ -21,3 +21,6 @@ "projects/hg.md" "sync/hg.md" "projects/svn.md" "sync/svn.md" # "cmd/git.md" "cmd/git/index.md" # dirhtml: same output path +"contributing/index.md" "project/contributing.md" +"contributing/workflow.md" "project/workflow.md" +"pytest-plugin.md" "api/pytest-plugin.md" diff --git a/docs/sync/index.md b/docs/sync/index.md index 3365aada8..46f53c84e 100644 --- a/docs/sync/index.md +++ b/docs/sync/index.md @@ -14,14 +14,36 @@ versions. ::: -```{toctree} -:caption: API +## Modules -git -hg -svn -base -``` +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Git Sync +:link: git +:link-type: doc +Clone, fetch, and update Git repositories. +::: + +:::{grid-item-card} Hg Sync +:link: hg +:link-type: doc +Clone and update Mercurial repositories. +::: + +:::{grid-item-card} SVN Sync +:link: svn +:link-type: doc +Checkout and update Subversion working copies. +::: + +:::{grid-item-card} Base +:link: base +:link-type: doc +Abstract base class for all sync backends. +::: + +:::: ## Constants @@ -29,3 +51,12 @@ base .. automodule:: libvcs.sync.constants :members: ``` + +```{toctree} +:hidden: + +git +hg +svn +base +``` diff --git a/docs/topics/index.md b/docs/topics/index.md index 4f281b886..53848b2fb 100644 --- a/docs/topics/index.md +++ b/docs/topics/index.md @@ -1,13 +1,33 @@ ---- -orphan: true ---- - # Topics Explore libvcs's core functionalities and design patterns at a high level, with detailed explanations and runnable examples. +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Traversing Git +:link: traversing_git +:link-type: doc +Walk branches, remotes, and tags with the command layer. +::: + +:::{grid-item-card} Filtering +:link: filtering +:link-type: doc +Query and filter collections by attributes. +::: + +:::{grid-item-card} URL Parsing +:link: url_parsing +:link-type: doc +Detect, validate, and normalize VCS URLs. +::: + +:::: + ```{toctree} +:hidden: traversing_git filtering diff --git a/docs/url/index.md b/docs/url/index.md index e7fdd2910..d02e72f5c 100644 --- a/docs/url/index.md +++ b/docs/url/index.md @@ -14,6 +14,49 @@ perfect balance. If we could make it ready-to-go out of the box, but also have framework-like extensibility, it could satisfy the niche. +## Modules + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Git URLs +:link: git +:link-type: doc +Parse and validate Git repository URLs (HTTPS, SSH, SCP). +::: + +:::{grid-item-card} SVN URLs +:link: svn +:link-type: doc +Parse Subversion repository URLs. +::: + +:::{grid-item-card} Hg URLs +:link: hg +:link-type: doc +Parse Mercurial repository URLs. +::: + +:::{grid-item-card} Base +:link: base +:link-type: doc +Abstract base classes for URL parsing. +::: + +:::{grid-item-card} Registry +:link: registry +:link-type: doc +URL matcher registration and lookup. +::: + +:::{grid-item-card} Constants +:link: constants +:link-type: doc +Shared regex patterns and URL constants. +::: + +:::: + ## Validate and detect VCS URLs ````{tab} git @@ -256,10 +299,8 @@ When a match occurs, its `defaults` will fill in non-matched groups. When registering new matchers, higher `weight`s are checked first. If it's a valid regex grouping, it will be picked. -## Explore - ```{toctree} -:caption: API +:hidden: git svn diff --git a/pyproject.toml b/pyproject.toml index 67e69467f..6f02138b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ dev = [ "sphinx-copybutton", "sphinxext-rediraffe", "myst-parser", + "sphinx-design", "linkify-it-py", # Testing "gp-libs", @@ -101,6 +102,7 @@ docs = [ "sphinx-copybutton", "sphinxext-rediraffe", "myst-parser", + "sphinx-design", "linkify-it-py", ] testing = [ diff --git a/uv.lock b/uv.lock index 1d76d9d84..5cb4365ad 100644 --- a/uv.lock +++ b/uv.lock @@ -551,6 +551,8 @@ dev = [ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -568,6 +570,8 @@ docs = [ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -611,6 +615,7 @@ dev = [ { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -624,6 +629,7 @@ docs = [ { name = "sphinx-autobuild" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, + { name = "sphinx-design" }, { name = "sphinx-inline-tabs" }, { name = "sphinxext-opengraph" }, { name = "sphinxext-rediraffe" }, @@ -1296,6 +1302,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + [[package]] name = "sphinx-inline-tabs" version = "2025.12.21.14"