Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ $ uv add libvcs --prerelease allow

_Upcoming changes will be written here._

### Documentation (#498)

Fix doctree warnings and broken references

## libvcs 0.38.0 (2025-11-30)

### New features
Expand Down
4 changes: 2 additions & 2 deletions MIGRATION
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ _Notes on the upcoming release will be added here_

<!-- Maintainers, insert migration notes for the next release here -->

#### pytest fixtures: `git_local_clone` renamed to `example_git_repo` (#468)
### pytest fixtures: `git_local_clone` renamed to `example_git_repo` (#468)

- pytest: `git_local_clone` renamed to `example_git_repo`

#### Commands: Listing method renamed (#466)
### Commands: Listing method renamed (#466)

- `libvcs.cmd.git.GitCmd._list()` -> `libvcs.cmd.git.Git.ls()`
- `libvcs.cmd.svn.Svn._list()` -> `libvcs.cmd.svn.Svn.ls()`
Expand Down
18 changes: 17 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
# sphinx.ext.autodoc
autoclass_content = "both"
autodoc_member_order = "bysource"
# Don't show class signature with the class' name.
autodoc_class_signature = "separated"
toc_object_entries_show_parents = "hide"
autodoc_default_options = {
"undoc-members": True,
Expand All @@ -120,7 +122,9 @@
}

# sphinx-autodoc-typehints
autodoc_typehints = "description" # show type hints in doc body instead of signature
# Automatically extract typehints when specified and place them in
# descriptions of the relevant function/method.
autodoc_typehints = "description"
simplify_optional_unions = True

# sphinx.ext.napoleon
Expand Down Expand Up @@ -151,6 +155,18 @@
"gp-libs": ("https://gp-libs.git-pull.com/", None),
}

# Keep network lookups fast and quiet when inventories are slow or unreachable.
intersphinx_timeout = 1

# sphinx-autodoc-typehints
# Suppress warnings for forward references that can't be resolved
# (types in TYPE_CHECKING blocks used for circular import avoidance)
suppress_warnings = [
"intersphinx",
"intersphinx.inventory",
"sphinx_autodoc_typehints.forward_reference",
]


def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str:
"""
Expand Down
6 changes: 1 addition & 5 deletions docs/pytest-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ Looking for more flexibility, correctness, or power? Need different defaults? [C
[connect with us]: https://github.com/vcs-python/libvcs/discussions
```

```{module} libvcs.pytest_plugin

```

[pytest]: https://docs.pytest.org/

## Usage
Expand All @@ -29,7 +25,7 @@ Pytest will automatically detect the plugin, and its fixtures will be available.

## Fixtures

This pytest plugin works by providing {ref}`pytest fixtures <pytest:fixtures-api>`. The plugin's fixtures ensure that a fresh Git, Subversion, or Mercurial repository is available for each test. It utilizes [session-scoped fixtures] to cache initial repositories, improving performance across tests.
This pytest plugin works by providing [pytest fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html). The plugin's fixtures ensure that a fresh Git, Subversion, or Mercurial repository is available for each test. It utilizes [session-scoped fixtures] to cache initial repositories, improving performance across tests.

[session-scoped fixtures]: https://docs.pytest.org/en/8.3.x/how-to/fixtures.html#fixture-scopes

Expand Down
9 changes: 3 additions & 6 deletions docs/url/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,9 @@ in {mod}`urlparse` (undocumented).

From there, `GitURL` can be used downstream directly by other projects.

In our case, `libvcs`s' own {ref}`cmd` and {ref}`projects`, as well as a {ref}`vcspull:index`
configuration, will be able to detect and accept various URL patterns.
In our case, `libvcs`s' own {ref}`cmd` and {ref}`projects`, as well as a
[vcspull configuration](https://vcspull.git-pull.com/), will be able to detect and accept various
URL patterns.

### Matchers: Defaults

Expand All @@ -255,10 +256,6 @@ 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.

[^api-unstable]: Provisional API only

It's not determined if Location will be mutable or if modifications will return a new object.

## Explore

```{toctree}
Expand Down
2 changes: 2 additions & 0 deletions src/libvcs/_internal/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

if t.TYPE_CHECKING:
from _typeshed import DataclassInstance
else:
DataclassInstance = object


class SkipDefaultFieldsReprMixin:
Expand Down
15 changes: 7 additions & 8 deletions src/libvcs/_internal/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def console_to_str(s: bytes) -> str:

if t.TYPE_CHECKING:
_LoggerAdapter = logging.LoggerAdapter[logging.Logger]
from typing import TypeAlias
else:
_LoggerAdapter = logging.LoggerAdapter

Expand Down Expand Up @@ -96,12 +95,12 @@ def __call__(self, output: str, timestamp: datetime.datetime) -> None:


if sys.platform == "win32":
_ENV: TypeAlias = Mapping[str, str]
_ENV: t.TypeAlias = Mapping[str, str]
else:
_ENV: TypeAlias = Mapping[bytes, StrPath] | Mapping[str, StrPath]
_ENV: t.TypeAlias = Mapping[bytes, StrPath] | Mapping[str, StrPath]

_CMD = StrPath | Sequence[StrPath]
_FILE: TypeAlias = int | t.IO[t.Any] | None
_FILE: t.TypeAlias = int | t.IO[t.Any] | None


def run(
Expand Down Expand Up @@ -158,12 +157,12 @@ def run(
subprocess in real time instead of when the process finishes.

check_returncode : bool
Indicate whether a `libvcs.exc.CommandError` should be raised if return code is
different from 0.
Indicate whether a ``libvcs.exc.CommandError`` should be raised if return
code is different from 0.

callback : ProgressCallbackProtocol
callback to return output as a command executes, accepts a function signature
of `(output, timestamp)`. Example usage::
of ``(output, timestamp)``. Example usage::

def progress_cb(output, timestamp):
sys.stdout.write(output)
Expand All @@ -172,7 +171,7 @@ def progress_cb(output, timestamp):

Upcoming changes
----------------
When minimum python >= 3.10, `pipesize: int = -1` will be added after `umask`.
When minimum python >= 3.10, pipesize: int = -1 will be added after umask.
"""
proc = subprocess.Popen(
args,
Expand Down
10 changes: 3 additions & 7 deletions src/libvcs/_internal/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@
import typing as t

from libvcs import GitSync, HgSync, SvnSync, exc
from libvcs._internal.run import ProgressCallbackProtocol
from libvcs._internal.types import StrPath, VCSLiteral
from libvcs.exc import InvalidVCS
from libvcs.url import registry as url_tools

if t.TYPE_CHECKING:
from typing import TypeGuard

from libvcs._internal.run import ProgressCallbackProtocol
from libvcs._internal.types import StrPath, VCSLiteral


class VCSNoMatchFoundForUrl(exc.LibVCSException):
def __init__(self, url: str, *args: object) -> None:
Expand Down Expand Up @@ -131,7 +127,7 @@ def create_project(

assert vcs_matches[0].vcs is not None

def is_vcs(val: t.Any) -> TypeGuard[VCSLiteral]:
def is_vcs(val: t.Any) -> t.TypeGuard[VCSLiteral]:
return isinstance(val, str) and val in {"git", "hg", "svn"}

if is_vcs(vcs_matches[0].vcs):
Expand Down
87 changes: 5 additions & 82 deletions src/libvcs/_internal/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@

from .dataclasses import SkipDefaultFieldsReprMixin

if t.TYPE_CHECKING:
from typing import TypeAlias


F = t.TypeVar("F", bound=t.Callable[..., t.Any])


Expand All @@ -64,92 +60,19 @@ def __init__(self, output: str, *args: object) -> None:


if sys.platform == "win32":
_ENV: TypeAlias = Mapping[str, str]
_ENV: t.TypeAlias = Mapping[str, str]
else:
_ENV: TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath]
_FILE: TypeAlias = None | int | t.IO[t.Any]
_TXT: TypeAlias = bytes | str
_ENV: t.TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath]
_FILE: t.TypeAlias = None | int | t.IO[t.Any]
_TXT: t.TypeAlias = bytes | str
#: Command
_CMD: TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath]
_CMD: t.TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath]


@dataclasses.dataclass(repr=False)
class SubprocessCommand(SkipDefaultFieldsReprMixin):
"""Wraps a :mod:`subprocess` request. Inspect, mutate, control before invocation.

Attributes
----------
args : _CMD
A string, or a sequence of program arguments.

bufsize : int
supplied as the buffering argument to the open() function when creating the
stdin/stdout/stderr pipe file objects

executable : Optional[StrOrBytesPath]
A replacement program to execute.

stdin : _FILE
standard output for executed program

stdout :
standard output for executed program

stderr :
standard output for executed program

close_fds : Controls closing or inheriting of file descriptors.

shell : If true, the command will be executed through the shell.

cwd : Sets the current directory before the child is executed.

env : Defines the environment variables for the new process.

text :
If ``True``, decode stdin, stdout and stderr using the given encoding (if set)
or the system default otherwise.

universal_newlines :
Alias of text, provided for backwards compatibility.

startupinfo :
Windows only

creationflags :
Windows only

preexec_fn :
(POSIX only) An object to be called in the child process just before the child
is executed.

restore_signals :
POSIX only

start_new_session :
POSIX only

group :
POSIX only

extra_groups :
POSIX only

user :
POSIX only

umask :
POSIX only

pass_fds :
POSIX only

encoding :
Text mode encoding to use for file objects stdin, stdout and stderr.

errors :
Text mode error handling to use for file objects stdin, stdout and stderr.

Examples
--------
>>> cmd = SubprocessCommand("ls")
Expand Down
11 changes: 4 additions & 7 deletions src/libvcs/_internal/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Internal :term:`type annotations <annotation>`.
"""Internal type annotations.

Notes
-----
Expand All @@ -12,14 +12,11 @@
import typing as t
from os import PathLike

if t.TYPE_CHECKING:
from typing import TypeAlias

StrPath: TypeAlias = str | PathLike[str] # stable
StrPath: t.TypeAlias = str | PathLike[str] # stable
""":class:`os.PathLike` or :class:`str`"""

StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes]
""":class:`os.PathLike`, :class:`str` or :term:`bytes-like object`"""
StrOrBytesPath: t.TypeAlias = str | bytes | PathLike[str] | PathLike[bytes]
""":class:`os.PathLike`, :class:`str` or bytes-like object"""


VCSLiteral = t.Literal["git", "svn", "hg"]
Expand Down
2 changes: 1 addition & 1 deletion src/libvcs/cmd/hg.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def pull(
) -> str:
"""Update working directory.

Wraps `hg update <https://www.mercurial-scm.org/doc/hg.1.html#pull>`_.
Wraps `hg pull <https://www.mercurial-scm.org/doc/hg.1.html#pull>`_.

Examples
--------
Expand Down
9 changes: 2 additions & 7 deletions src/libvcs/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@
import pytest

from libvcs import exc
from libvcs._internal.run import run
from libvcs._internal.run import _ENV, run
from libvcs.sync.git import GitRemote, GitSync
from libvcs.sync.hg import HgSync
from libvcs.sync.svn import SvnSync

if t.TYPE_CHECKING:
from typing import TypeAlias

from libvcs._internal.run import _ENV


class MaxUniqueRepoAttemptsExceeded(exc.LibVCSException):
"""Raised when exceeded threshold of attempts to find a unique repo destination."""
Expand Down Expand Up @@ -270,7 +265,7 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) ->
return remote_repo_name


InitCmdArgs: TypeAlias = list[str] | None
InitCmdArgs: t.TypeAlias = list[str] | None


class CreateRepoPostInitFn(t.Protocol):
Expand Down
4 changes: 1 addition & 3 deletions src/libvcs/sync/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
from urllib import parse as urlparse

from libvcs._internal.run import _CMD, CmdLoggingAdapter, ProgressCallbackProtocol, run

if t.TYPE_CHECKING:
from libvcs._internal.types import StrPath
from libvcs._internal.types import StrPath

logger = logging.getLogger(__name__)

Expand Down
4 changes: 1 addition & 3 deletions src/libvcs/sync/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@
from urllib import parse as urlparse

from libvcs import exc
from libvcs._internal.types import StrPath
from libvcs.cmd.git import Git
from libvcs.sync.base import (
BaseSync,
VCSLocation,
convert_pip_url as base_convert_pip_url,
)

if t.TYPE_CHECKING:
from libvcs._internal.types import StrPath

logger = logging.getLogger(__name__)


Expand Down
Loading