Skip to content

Commit 15b4142

Browse files
committed
docs(fix): tighten API docs links
why: Keep documentation examples executable and cross-references aligned with the rendered API surface. what: - Replace stale pytest-plugin directive and convert illustrative Python fences to doctests - Link first mentions and add concept-first leads across docs pages - Repair source docstring xrefs and skipped doctests that feed API docs
1 parent 1383af8 commit 15b4142

30 files changed

Lines changed: 221 additions & 155 deletions

MIGRATION

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,16 @@ references these fixtures.
6969

7070
`URL.rule_map` is now a class attribute rather than a dataclass attribute.
7171

72-
Before:
72+
Before Python 3.11 rejected mutable dataclass defaults:
7373

7474
```python
75-
@dataclasses.dataclass(repr=False)
76-
class GitLabURL(GitURL):
77-
rule_map: RuleMap = RuleMap(
78-
_rule_map={'gitlab_prefix': GitLabPrefix}
79-
)
80-
```
81-
82-
In python 3.11, that raises an error:
83-
84-
```console
85-
File "/home/user/.python/3.11.0/lib/python3.11/dataclasses.py", line 1211, in wrap
86-
return _process_class(cls, init, repr, eq, order, unsafe_hash,
87-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88-
File "/home/user/.python/3.11.0/lib/python3.11/dataclasses.py", line 959, in _process_class
89-
cls_fields.append(_get_field(cls, name, type, kw_only))
90-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91-
File "/home/user/.python/3.11.0/lib/python3.11/dataclasses.py", line 816, in _get_field
92-
raise ValueError(f'mutable default {type(f.default)} for field '
75+
>>> import dataclasses
76+
>>> from libvcs.url.base import RuleMap
77+
>>> @dataclasses.dataclass(repr=False)
78+
... class GitLabURL:
79+
... rule_map: RuleMap = RuleMap(_rule_map={})
80+
Traceback (most recent call last):
81+
...
9382
ValueError: mutable default <class 'libvcs.url.base.RuleMap'> for field rule_map is not allowed: use default_factory
9483
```
9584

docs/api/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
# API Reference
66

77
libvcs exposes three public subsystems -- URL parsing, command execution,
8-
and repository synchronization -- plus a pytest plugin for test fixtures.
8+
and repository synchronization -- plus a {doc}`pytest plugin <pytest-plugin>`
9+
for test fixtures.
910

1011
All APIs are pre-1.0 and may change between minor versions.
1112
Pin to a range, e.g. `libvcs>=0.45,<0.46`.

docs/api/pytest-plugin.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# `pytest` Plugin
44

5-
:::{doc-pytest-plugin} libvcs.pytest_plugin
5+
:::{auto-pytest-plugin} libvcs.pytest_plugin
66
:project: libvcs
77
:package: libvcs
88
:summary: libvcs ships a pytest plugin for creating isolated Git, Mercurial, and Subversion repositories during tests.
@@ -29,16 +29,14 @@ Keep autouse setup explicit in your own `conftest.py` instead of having the
2929
plugin force global side effects.
3030

3131
```python
32-
import pytest
33-
34-
35-
@pytest.fixture(autouse=True)
36-
def setup(
37-
set_home: None,
38-
set_vcs_gitconfig: None,
39-
set_vcs_hgconfig: None,
40-
) -> None:
41-
pass
32+
>>> import pytest
33+
>>> @pytest.fixture(autouse=True)
34+
... def setup(
35+
... set_home: None,
36+
... set_vcs_gitconfig: None,
37+
... set_vcs_hgconfig: None,
38+
... ) -> None:
39+
... pass
4240
```
4341

4442
## Repository isolation

docs/cmd/git/index.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
# `libvcs.cmd.git`
22

3-
For [`git(1)`](https://git-scm.com/docs/git).
3+
Use {class}`~libvcs.cmd.git.Git` when you need direct access to
4+
[`git(1)`](https://git-scm.com/docs/git) from Python, with typed helpers for
5+
common subcommands and an escape hatch to run raw git arguments.
46

57
_Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module),
68
[`salt.modules.git`](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.git.html),
79
[`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html)_
810

911
## Managers and Commands
1012

11-
libvcs provides **Managers** and **Commands** for git subcommands:
13+
libvcs provides {ref}`Managers and Commands <traversing-git-repos>` for git
14+
subcommands:
1215

1316
- **Managers** (`git.branches`, `git.tags`, etc.) let you traverse repository
1417
entities intuitively with ORM-like filtering via

docs/cmd/git/reflog.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ For [`git-reflog(1)`](https://git-scm.com/docs/git-reflog).
44

55
## Overview
66

7-
Manage git reflog using {class}`~libvcs.cmd.git.GitReflogManager` (collection-level)
8-
and {class}`~libvcs.cmd.git.GitReflogEntryCmd` (per-entry operations).
7+
Manage git reflog using {class}`~libvcs.cmd.git.GitReflogManager`
8+
(collection-level), {class}`~libvcs.cmd.git.GitReflogEntry` (entry data), and
9+
{class}`~libvcs.cmd.git.GitReflogEntryCmd` (per-entry operations).
910

1011
### Examples
1112

docs/cmd/index.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
# Commands - `libvcs.cmd`
44

5-
Run git, hg, and svn from Python through typed wrappers — one class per VCS
6-
binary, one method per operation.
5+
Run [`git(1)`], [`hg(1)`], and [`svn(1)`] from Python through typed wrappers
6+
— one class per VCS binary, one method per operation.
77

88
Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module),
99
[`salt.modules.git`](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.git.html),
@@ -20,7 +20,7 @@ versions.
2020

2121
The {mod}`libvcs.cmd` module provides Python wrappers for VCS command-line tools:
2222

23-
- {mod}`libvcs.cmd.git` - Git commands with {ref}`Managers <traversing-git-repos>` for intuitive entity traversal and Commands for targeted execution
23+
- {mod}`libvcs.cmd.git` - Git commands with {ref}`Managers and Commands <traversing-git-repos>` for intuitive entity traversal and targeted execution
2424
- {mod}`libvcs.cmd.hg` - Mercurial commands
2525
- {mod}`libvcs.cmd.svn` - Subversion commands
2626

@@ -63,3 +63,7 @@ git/index
6363
hg
6464
svn
6565
```
66+
67+
[`git(1)`]: https://git-scm.com/docs/git
68+
[`hg(1)`]: https://www.mercurial-scm.org/doc/hg.1.html
69+
[`svn(1)`]: https://svnbook.red-bean.com/en/1.7/svn.ref.svn.html

docs/index.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# libvcs
44

5-
Typed Python utilities for Git, SVN, and Mercurial. Parse URLs,
5+
Typed Python utilities for [Git], [Subversion], and [Mercurial]. Parse URLs,
66
execute commands, and synchronize repositories -- all with a
77
consistent, type-friendly API.
88

@@ -92,11 +92,15 @@ libvcs ships a {doc}`pytest plugin </api/pytest-plugin>` with
9292
session-scoped fixtures for Git, SVN, and Mercurial repositories:
9393

9494
```python
95-
def test_my_tool(create_git_remote_repo):
96-
repo_path = create_git_remote_repo()
97-
assert repo_path.exists()
95+
>>> def test_my_tool(create_git_remote_repo):
96+
... repo_path = create_git_remote_repo()
97+
... assert repo_path.exists()
9898
```
9999

100+
[Git]: https://git-scm.com/docs/git
101+
[Subversion]: https://subversion.apache.org/
102+
[Mercurial]: https://www.mercurial-scm.org/
103+
100104
```{toctree}
101105
:hidden:
102106

docs/project/adr/0001-faithful-subprocess-output-capture.md

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ Proposed. 2026-06-20.
88

99
## Context
1010

11-
The legacy command runner `libvcs._internal.run.run` — used by every
12-
`Git`, `Hg`, and `Svn` command class via `.run()` — does not return what
13-
the underlying VCS actually printed. After the process exits it splits
11+
The legacy command runner {func}`libvcs._internal.run.run` — used by every
12+
{class}`~libvcs.cmd.git.Git`, {class}`~libvcs.cmd.hg.Hg`, and
13+
{class}`~libvcs.cmd.svn.Svn` command class via `run()` — does not return
14+
what the underlying VCS actually printed. After the process exits it splits
1415
captured stdout into lines, calls `bytes.strip()` on each line, drops any
1516
line that is empty after stripping, and rejoins with `\n` and no trailing
1617
newline. stderr is treated the same way and then rejoined with no
@@ -41,8 +42,8 @@ human-readable progress lines, not to capture structured output. The
4142
runner's own module docstring already states that it "will be deprecated
4243
by `libvcs._internal.subprocess`".
4344

44-
`libvcs._internal.subprocess.SubprocessCommand` already exists as a thin,
45-
typed wrapper that returns a real `subprocess.CompletedProcess` with
45+
{class}`~libvcs._internal.subprocess.SubprocessCommand` already exists as a thin,
46+
typed wrapper that returns a real {class}`subprocess.CompletedProcess` with
4647
separate, untouched `stdout` and `stderr`. It is bytes-first with opt-in
4748
text decoding, and it is currently wired into nothing.
4849

@@ -73,8 +74,8 @@ capture path. This is implemented in two phases so the stable
7374
### Phase 2 — pristine structured backend
7475

7576
- Route the `cmd/*` classes through
76-
`libvcs._internal.subprocess.SubprocessCommand`, which returns a
77-
`subprocess.CompletedProcess` (bytes-first, separate
77+
{class}`~libvcs._internal.subprocess.SubprocessCommand`, which returns a
78+
{class}`subprocess.CompletedProcess` (bytes-first, separate
7879
stdout/stderr/returncode).
7980
- Expose a structured accessor that returns the `CompletedProcess` for
8081
callers that want streams, exit code, and exact bytes. Keep
@@ -102,7 +103,7 @@ The decisive measurement: flipping the default to verbatim broke only
102103
doctests in `cmd/git.py`, `cmd/hg.py`, and `cmd/svn.py` — example output
103104
that gained a trailing newline. No functional test, sync-layer call, or
104105
downstream consumer broke, because those already strip where they need a
105-
bare value (`vcspull`, like the sync layer, trims defensively). A global
106+
bare value ([vcspull], like the sync layer, trims defensively). A global
106107
trailing trim, by contrast, cannot produce an applyable patch: the
107108
patch's required final newline is exactly what it strips. So verbatim
108109
becomes the default — fixing the original `git apply` failure for the
@@ -122,7 +123,7 @@ reflects the true, faithful output.
122123
byte-identical; error messages keep their line structure.
123124
- The default now returns output with its trailing newline. Callers that
124125
want a bare value pass `trim=True`; existing consumers are unaffected
125-
because the sync layer and `vcspull` already strip defensively before
126+
because the sync layer and [vcspull] already strip defensively before
126127
comparing.
127128
- The stderr concatenation defect is removed: error lines keep their
128129
separators. (Phase 2's structured backend additionally avoids the
@@ -150,19 +151,28 @@ reflects the true, faithful output.
150151
The decision follows the convergent practice of mature VCS and
151152
subprocess-wrapping tools, none of which trim inside the capture path:
152153

153-
- **pip** added a per-call mode (`stdout_only`) that returns VCS output
154+
- **[pip]** added a per-call mode (`stdout_only`) that returns VCS output
154155
verbatim, and replaced its `console_to_str` helper with
155156
`errors="backslashreplace"`.
156-
- **uv** captures into a raw `Output { stdout, stderr }` and applies
157+
- **[uv]** captures into a raw `Output { stdout, stderr }` and applies
157158
`trim_end()` at each call site for scalar reads.
158-
- **mise** relies on whole-output trailing trim for scalars, decodes
159+
- **[mise]** relies on whole-output trailing trim for scalars, decodes
159160
strictly for data and lossily for stderr.
160-
- **Mercurial**, **gitoxide**, and **Jujutsu** are bytes-first and keep
161+
- **[Mercurial]**, **[gitoxide]**, and **[Jujutsu]** are bytes-first and keep
161162
output verbatim; gitoxide treats newline-stripping as a named, opt-in
162163
view, and tracks the missing-final-newline case explicitly.
163-
- **git** itself confirms the constraint: its patch parser requires each
164+
- **[git]** itself confirms the constraint: its patch parser requires each
164165
line, including the last, to be newline-terminated.
165166

166167
The lesson shared by all of them: capture pristine, keep streams
167168
separate, and make trimming and decoding explicit edge transformations.
168169
`SubprocessCommand` already embodies that shape inside libvcs.
170+
171+
[vcspull]: https://vcspull.git-pull.com/
172+
[pip]: https://pip.pypa.io/
173+
[uv]: https://docs.astral.sh/uv/
174+
[mise]: https://mise.jdx.dev/
175+
[Mercurial]: https://www.mercurial-scm.org/
176+
[gitoxide]: https://github.com/GitoxideLabs/gitoxide
177+
[Jujutsu]: https://jj-vcs.github.io/jj/
178+
[git]: https://git-scm.com/

docs/project/adr/0002-order-independent-tests.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ the suite is **order-independent**: every test must pass regardless of what ran
1818
before it. The suite was not. Two pieces of shared mutable state passed in the
1919
fixed collection order but failed once tests were reordered:
2020

21-
- `libvcs.url.registry.VCSRegistry` stored its `parser_map` in a class
21+
- {class}`~libvcs.url.registry.VCSRegistry` stored its `parser_map` in a class
2222
variable, so constructing any second registry mutated the module-level
2323
`registry` singleton — whichever test built a custom registry first changed
2424
URL detection for every later test.
@@ -28,9 +28,10 @@ fixed collection order but failed once tests were reordered:
2828
every later test that copied it.
2929

3030
These were invisible under the default order and surfaced only when a
31-
shuffled run (`pytest-randomly`) or a parallel run (`pytest-xdist`) changed
31+
shuffled run ([pytest-randomly]) or a parallel run ([pytest-xdist]) changed
3232
which tests ran together and in what sequence. They are also genuine bugs for
33-
downstream consumers (e.g. vcspull) that build on `VCSRegistry` and the
33+
downstream consumers (e.g. [vcspull]) that build on
34+
{class}`~libvcs.url.registry.VCSRegistry` and the
3435
fixtures, independent of how the tests run.
3536

3637
## Decision
@@ -56,7 +57,7 @@ py.test -p randomly`).
5657

5758
### Parallelism is opt-in, not the default
5859

59-
`pytest-xdist` is a development dependency exposed via `just test-parallel`
60+
[pytest-xdist] is a development dependency exposed via `just test-parallel`
6061
(`uv run py.test -n auto`). The default `uv run py.test` stays serial.
6162

6263
- The worker count is **not** hardcoded. `-n auto` adapts to the machine; the
@@ -88,7 +89,8 @@ checks (`uv run --with pytest-randomly`) rather than committed.
8889
### Positive
8990

9091
- The suite passes under serial, shuffled, and parallel (`-n auto`) execution.
91-
- Two real isolation bugs in shipped code (`VCSRegistry`, the repo fixtures)
92+
- Two real isolation bugs in shipped code
93+
({class}`~libvcs.url.registry.VCSRegistry`, the repo fixtures)
9294
are fixed, benefiting downstream consumers regardless of parallelism.
9395
- A faster opt-in run is available without changing the stable default.
9496

@@ -108,11 +110,15 @@ checks (`uv run --with pytest-randomly`) rather than committed.
108110

109111
## Prior art
110112

111-
- `pytest-xdist` provides the `load`, `loadscope`, and `loadfile` schedulers;
113+
- [pytest-xdist] provides the `load`, `loadscope`, and `loadfile` schedulers;
112114
its guidance is that tests must be independent for `load` to be safe.
113-
- `pytest-randomly` randomizes order specifically to surface inter-test
115+
- [pytest-randomly] randomizes order specifically to surface inter-test
114116
coupling.
115117
- The broader convention across test suites: parallel execution requires
116118
order-independent tests, and shared mutable state (global registries, cached
117119
fixtures handed out by reference) is the usual cause of order-dependent
118120
failures.
121+
122+
[pytest-randomly]: https://github.com/pytest-dev/pytest-randomly
123+
[pytest-xdist]: https://pytest-xdist.readthedocs.io/
124+
[vcspull]: https://vcspull.git-pull.com/

docs/project/code-style.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# Code Style
44

5+
Use this page when you are changing Python code or docstrings and need the
6+
project's formatting, typing, and import conventions. The command examples are
7+
the common local checks; the root contributor guide still owns the full
8+
pre-commit gate.
9+
510
## Formatting and linting
611

712
libvcs uses [ruff](https://ruff.rs) for formatting **and** linting in a
@@ -21,29 +26,30 @@ $ uv run ruff check . --fix --show-fixes
2126
[mypy](http://mypy-lang.org/) runs in strict mode:
2227

2328
```console
24-
$ uv run mypy src tests
29+
$ uv run mypy .
2530
```
2631

2732
## Docstrings
2833

2934
All public APIs use **NumPy-style** docstrings:
3035

3136
```python
32-
def fetch(url: str, *, branch: str | None = None) -> str:
33-
"""Fetch a remote branch.
34-
35-
Parameters
36-
----------
37-
url : str
38-
Repository URL.
39-
branch : str or None
40-
Branch name. ``None`` means the default branch.
41-
42-
Returns
43-
-------
44-
str
45-
The fetched commit hash.
46-
"""
37+
>>> def fetch(url: str, *, branch: str | None = None) -> str:
38+
... """Fetch a remote branch.
39+
...
40+
... Parameters
41+
... ----------
42+
... url : str
43+
... Repository URL.
44+
... branch : str or None
45+
... Branch name. ``None`` means the default branch.
46+
...
47+
... Returns
48+
... -------
49+
... str
50+
... The fetched commit hash.
51+
... """
52+
... return "abc123"
4753
```
4854

4955
## Imports

0 commit comments

Comments
 (0)