# API Reference Source: https://libvcs.git-pull.com/api/ (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 ``` --- # pytest Plugin Source: https://libvcs.git-pull.com/api/pytest-plugin/ (pytest_plugin)= # `pytest` Plugin :::{doc-pytest-plugin} libvcs.pytest_plugin :project: libvcs :package: libvcs :summary: libvcs ships a pytest plugin for creating isolated Git, Mercurial, and Subversion repositories during tests. :tests-url: https://github.com/vcs-python/libvcs/tree/master/tests Use these fixtures when your tests need disposable repositories, config files, and home-directory setup without repeating bootstrap code in every suite. ## Recommended fixtures These fixtures are the usual starting point when enabling the plugin: - {fixture}`set_home` patches `$HOME` to point at {fixture}`user_path`. - {fixture}`set_vcs_gitconfig` and {fixture}`set_vcs_hgconfig` apply stable VCS configuration. - {fixture}`vcs_name`, {fixture}`vcs_email`, and {fixture}`vcs_user` let you override commit identity defaults. - {fixture}`git_commit_envvars` helps when Git ignores `GIT_CONFIG` in a subprocess-heavy test. ## Bootstrapping in `conftest.py` Keep autouse setup explicit in your own `conftest.py` instead of having the plugin force global side effects. ```python import pytest @pytest.fixture(autouse=True) def setup( set_home: None, set_vcs_gitconfig: None, set_vcs_hgconfig: None, ) -> None: pass ``` ::: ## Types ```{eval-rst} .. autodata:: libvcs.pytest_plugin.GitCommitEnvVars .. autoclass:: libvcs.pytest_plugin.CreateRepoFn :special-members: __call__ :exclude-members: __init__, _abc_impl, _is_protocol .. autoclass:: libvcs.pytest_plugin.CreateRepoPostInitFn :special-members: __call__ :exclude-members: __init__, _abc_impl, _is_protocol ``` --- # branch Source: https://libvcs.git-pull.com/cmd/git/branch/ # `branch` For `git-branch(1)`. ## Overview Manage git branches using {class}`~libvcs.cmd.git.GitBranchManager` (collection-level) and {class}`~libvcs.cmd.git.GitBranchCmd` (per-branch operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List all branches branches = git.branches.ls() # List remote branches only remote_branches = git.branches.ls(remotes=True) # Create a new branch git.branches.create('feature-branch') # Get a specific branch and operate on it branch = git.branches.get(branch_name='feature-branch') branch.rename('new-feature') branch.delete() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitBranchManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitBranchCmd :members: :show-inheritance: :undoc-members: ``` --- # libvcs.cmd.git Source: https://libvcs.git-pull.com/cmd/git/ # `libvcs.cmd.git` For `git(1)`. _Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module), [`salt.modules.git`](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.git.html), [`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html)_ ## Managers and Commands libvcs provides **Managers** and **Commands** for git subcommands: - **Managers** (`git.branches`, `git.tags`, etc.) let you traverse repository entities intuitively with ORM-like filtering via QueryList - **Commands** are contextual ways to run git commands against a specific target entity ``` Git instance ├── branches: GitBranchManager │ ├── ls() -> QueryList[GitBranchCmd] │ ├── get() -> GitBranchCmd │ └── create() ├── tags: GitTagManager ├── remotes: GitRemoteManager ├── stashes: GitStashManager ├── worktrees: GitWorktreeManager ├── notes: GitNotesManager ├── submodules: GitSubmoduleManager └── reflog: GitReflogManager ``` ### Quick Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List all branches branches = git.branches.ls() # Filter to remote branches only remote_branches = git.branches.ls(remotes=True) # Get a specific branch and rename it branch = git.branches.get(branch_name='old-name') branch.rename('new-name') # Create and manage tags git.tags.create(name='v1.0.0', message='Release 1.0') tag = git.tags.get(tag_name='v1.0.0') tag.delete() ``` ```{toctree} :caption: Subcommands :maxdepth: 1 submodule remote stash branch tag worktree notes reflog ``` ```{eval-rst} .. automodule:: libvcs.cmd.git :members: :show-inheritance: :undoc-members: :exclude-members: GitSubmoduleCmd, GitSubmoduleManager, GitSubmodule, GitSubmoduleEntryCmd, GitRemoteCmd, GitRemoteManager, GitStashCmd, GitStashManager, GitStashEntryCmd, GitBranchCmd, GitBranchManager, GitTagCmd, GitTagManager, GitWorktreeCmd, GitWorktreeManager, GitNoteCmd, GitNotesManager, GitReflogEntry, GitReflogEntryCmd, GitReflogManager ``` --- # notes Source: https://libvcs.git-pull.com/cmd/git/notes/ # `notes` For `git-notes(1)`. ## Overview Manage git notes using {class}`~libvcs.cmd.git.GitNotesManager` (collection-level) and {class}`~libvcs.cmd.git.GitNoteCmd` (per-note operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Add a note to a commit git.notes.add(object='HEAD', message='This is a note') # List all notes notes = git.notes.ls() # Get a specific note and operate on it note = git.notes.get(object='HEAD') note.show() note.append(message='Additional info') note.remove() # Prune notes for non-existent objects git.notes.prune() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitNotesManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitNoteCmd :members: :show-inheritance: :undoc-members: ``` --- # reflog Source: https://libvcs.git-pull.com/cmd/git/reflog/ # `reflog` For `git-reflog(1)`. ## Overview Manage git reflog using {class}`~libvcs.cmd.git.GitReflogManager` (collection-level) and {class}`~libvcs.cmd.git.GitReflogEntryCmd` (per-entry operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List reflog entries entries = git.reflog.ls() # List entries for a specific ref head_entries = git.reflog.ls(ref='HEAD') # Check if reflog exists for a ref git.reflog.exists(ref='main') # Expire old reflog entries git.reflog.expire(ref='HEAD', expire='90.days.ago') ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitReflogManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitReflogEntryCmd :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitReflogEntry :members: :show-inheritance: :undoc-members: ``` --- # remote Source: https://libvcs.git-pull.com/cmd/git/remote/ # `remote` For `git-remote(1)`. ## Overview Manage git remotes using {class}`~libvcs.cmd.git.GitRemoteManager` (collection-level) and {class}`~libvcs.cmd.git.GitRemoteCmd` (per-remote operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List all remotes remotes = git.remotes.ls() # Add a new remote git.remotes.add(name='upstream', url='https://github.com/org/repo.git') # Get a specific remote and operate on it origin = git.remotes.get(remote_name='origin') origin.show() origin.prune() origin.set_url('https://new-url.git') ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitRemoteManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitRemoteCmd :members: :show-inheritance: :undoc-members: ``` --- # stash Source: https://libvcs.git-pull.com/cmd/git/stash/ # `stash` For `git-stash(1)`. ## Overview Manage git stashes using {class}`~libvcs.cmd.git.GitStashManager` (collection-level) and {class}`~libvcs.cmd.git.GitStashEntryCmd` (per-stash operations). :::{note} {class}`~libvcs.cmd.git.GitStashCmd` is the legacy interface. Use `git.stashes` ({class}`~libvcs.cmd.git.GitStashManager`) for the new Manager/Cmd pattern. ::: ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Push changes to stash git.stashes.push(message='Work in progress') # List all stashes stashes = git.stashes.ls() # Get a specific stash and operate on it stash = git.stashes.get(index=0) stash.show() stash.apply() stash.drop() # Clear all stashes git.stashes.clear() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitStashManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitStashEntryCmd :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitStashCmd :members: :show-inheritance: :undoc-members: ``` --- # submodule Source: https://libvcs.git-pull.com/cmd/git/submodule/ # `submodule` For `git-submodule(1)`. ## Overview Manage git submodules using {class}`~libvcs.cmd.git.GitSubmoduleManager` (collection-level) and {class}`~libvcs.cmd.git.GitSubmoduleEntryCmd` (per-submodule operations). :::{note} {class}`~libvcs.cmd.git.GitSubmoduleCmd` is the legacy interface. Use `git.submodules` ({class}`~libvcs.cmd.git.GitSubmoduleManager`) for the new Manager/Cmd pattern. ::: ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Add a submodule git.submodules.add(url='https://github.com/org/lib.git', path='vendor/lib') # List all submodules submodules = git.submodules.ls() # Get a specific submodule and operate on it submodule = git.submodules.get(path='vendor/lib') submodule.init() submodule.update() submodule.deinit() # Sync submodule URLs git.submodules.sync() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitSubmoduleManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitSubmodule :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitSubmoduleEntryCmd :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitSubmoduleCmd :members: :show-inheritance: :undoc-members: ``` --- # tag Source: https://libvcs.git-pull.com/cmd/git/tag/ # `tag` For `git-tag(1)`. ## Overview Manage git tags using {class}`~libvcs.cmd.git.GitTagManager` (collection-level) and {class}`~libvcs.cmd.git.GitTagCmd` (per-tag operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Create a tag git.tags.create(name='v1.0.0', message='Release 1.0.0') # List all tags tags = git.tags.ls() # Filter tags release_tags = git.tags.ls(pattern='v*') # Get a specific tag and operate on it tag = git.tags.get(tag_name='v1.0.0') tag.show() tag.delete() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitTagManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitTagCmd :members: :show-inheritance: :undoc-members: ``` --- # worktree Source: https://libvcs.git-pull.com/cmd/git/worktree/ # `worktree` For `git-worktree(1)`. ## Overview Manage git worktrees using {class}`~libvcs.cmd.git.GitWorktreeManager` (collection-level) and {class}`~libvcs.cmd.git.GitWorktreeCmd` (per-worktree operations). ### Example ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List all worktrees worktrees = git.worktrees.ls() # Add a new worktree git.worktrees.add(path='/path/to/worktree', branch='feature-branch') # Get a specific worktree and operate on it wt = git.worktrees.get(worktree_path='/path/to/worktree') wt.lock(reason='Do not delete') wt.unlock() wt.remove() # Prune stale worktrees git.worktrees.prune() ``` ## API Reference ```{eval-rst} .. autoclass:: libvcs.cmd.git.GitWorktreeManager :members: :show-inheritance: :undoc-members: .. autoclass:: libvcs.cmd.git.GitWorktreeCmd :members: :show-inheritance: :undoc-members: ``` --- # libvcs.cmd.hg Source: https://libvcs.git-pull.com/cmd/hg/ # `libvcs.cmd.hg` For mercurial, aka `hg(1)`. ```{eval-rst} .. automodule:: libvcs.cmd.hg :members: :show-inheritance: :undoc-members: ``` --- # Commands - libvcs.cmd Source: https://libvcs.git-pull.com/cmd/ (cmd)= # Commands - `libvcs.cmd` Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module), [`salt.modules.git`](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.git.html), [`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html) :::{warning} All APIs are considered experimental and subject to break pre-1.0. They can and will break between versions. ::: ## Overview The `libvcs.cmd` module provides Python wrappers for VCS command-line tools: - {mod}`libvcs.cmd.git` - Git commands with Managers for intuitive entity traversal and Commands for targeted execution - {mod}`libvcs.cmd.hg` - Mercurial commands - {mod}`libvcs.cmd.svn` - Subversion commands ### When to use `cmd` vs `sync` | Module | Use Case | |--------|----------| | `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} :hidden: git/index hg svn ``` --- # libvcs.cmd.svn Source: https://libvcs.git-pull.com/cmd/svn/ # `libvcs.cmd.svn` For subversion, aka `svn(1)` ```{eval-rst} .. automodule:: libvcs.cmd.svn :members: :show-inheritance: :undoc-members: ``` --- # Changelog Source: https://libvcs.git-pull.com/history/ (changes)= (changelog)= (history)= ```{include} ../CHANGES ``` --- # libvcs Source: https://libvcs.git-pull.com/ (index)= # libvcs 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 ``` ```console $ uv add libvcs ``` ```{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/) 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} :hidden: quickstart topics/index api/index internals/index project/index history migration GitHub ``` --- # Dataclass helpers - libvcs._internal.dataclasses Source: https://libvcs.git-pull.com/internals/dataclasses/ # Dataclass helpers - `libvcs._internal.dataclasses` ```{eval-rst} .. automodule:: libvcs._internal.dataclasses :members: :special-members: ``` --- # Exceptions - libvcs.exc Source: https://libvcs.git-pull.com/internals/exc/ # Exceptions - `libvcs.exc` ```{eval-rst} .. automodule:: libvcs.exc :members: :show-inheritance: :undoc-members: ``` --- # Internals Source: https://libvcs.git-pull.com/internals/ (internals)= # Internals :::{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/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 query_list run subprocess shortcuts ``` --- # List querying - libvcs._internal.query_list Source: https://libvcs.git-pull.com/internals/query_list/ # List querying - `libvcs._internal.query_list` `QueryList` is the backbone of the Manager/Cmd pattern. Every `ls()` method in libvcs returns a `QueryList`, enabling chainable filtering on the results. ## How It's Used All Manager classes return `QueryList` from their `ls()` methods: ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Each ls() returns a QueryList branches = git.branches.ls() # QueryList[GitBranchCmd] tags = git.tags.ls() # QueryList[GitTagCmd] remotes = git.remotes.ls() # QueryList[GitRemoteCmd] stashes = git.stashes.ls() # QueryList[GitStashEntryCmd] worktrees = git.worktrees.ls() # QueryList[GitWorktreeCmd] ``` ## Filtering `QueryList` extends Python's built-in `list` with Django-style lookups: ```python # Exact match branches.filter(name='main') # Case-insensitive contains branches.filter(name__icontains='feature') # Nested attribute access branches.filter(commit__sha__startswith='abc123') ``` ### Available Lookups | Lookup | Description | |--------|-------------| | `exact` | Exact match (default) | | `iexact` | Case-insensitive exact match | | `contains` | Substring match | | `icontains` | Case-insensitive substring | | `startswith` | Prefix match | | `istartswith` | Case-insensitive prefix | | `endswith` | Suffix match | | `iendswith` | Case-insensitive suffix | | `in` | Value in list | | `nin` | Value not in list | | `regex` | Regular expression match | | `iregex` | Case-insensitive regex | ### Chaining Filters can be chained and combined: ```python # Multiple conditions (AND) branches.filter(name__startswith='feature', is_remote=False) # Get single result branches.get(name='main') # Chain filters branches.filter(is_remote=True).filter(name__contains='release') ``` ## API Reference ```{eval-rst} .. automodule:: libvcs._internal.query_list :members: ``` --- # Command helpers - libvcs._internal.run Source: https://libvcs.git-pull.com/internals/run/ # Command helpers - `libvcs._internal.run` ```{eval-rst} .. automodule:: libvcs._internal.run :members: :show-inheritance: :undoc-members: ``` --- # Shortcuts - libvcs._internal.shortcuts Source: https://libvcs.git-pull.com/internals/shortcuts/ # Shortcuts - `libvcs._internal.shortcuts` ```{eval-rst} .. automodule:: libvcs._internal.shortcuts :members: :show-inheritance: :undoc-members: ``` --- # SubprocessCommand - libvcs._internal.subprocess Source: https://libvcs.git-pull.com/internals/subprocess/ # SubprocessCommand - `libvcs._internal.subprocess` ```{eval-rst} .. automodule:: libvcs._internal.subprocess :members: ``` --- # Typing utilities - libvcs._internal.types Source: https://libvcs.git-pull.com/internals/types/ # Typing utilities - `libvcs._internal.types` ```{eval-rst} .. automodule:: libvcs._internal.types :members: :show-inheritance: :undoc-members: ``` --- # Migration notes Source: https://libvcs.git-pull.com/migration/ (migration)= ```{currentmodule} libvcs ``` ```{include} ../MIGRATION ``` --- # Code Style Source: https://libvcs.git-pull.com/project/code-style/ (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. --- # Contributing Source: https://libvcs.git-pull.com/project/contributing/ (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. --- # Project Source: https://libvcs.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} 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 ``` --- # Releasing Source: https://libvcs.git-pull.com/project/releasing/ (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 ``` --- # Workflow Source: https://libvcs.git-pull.com/project/workflow/ (workflow)= # Workflow ## Development environment [uv] is a required package to develop. ```console $ git clone https://github.com/vcs-python/libvcs.git ``` ```console $ cd libvcs ``` ```console $ uv install -E "docs test coverage lint" ``` Justfile commands prefixed with `watch-` will watch files and rerun. ## Tests ```console $ uv run py.test ``` Helpers: `just test` Rerun tests on file change: `just watch-test` (requires [entr(1)]) ## Documentation Default preview server: http://localhost:8068 [sphinx-autobuild] will automatically build the docs, watch for file changes and launch a server. From home directory: `just start-docs` From inside `docs/`: `just start` [sphinx-autobuild]: https://github.com/executablebooks/sphinx-autobuild ### Manual documentation (the hard way) `cd docs/` and `just html` to build. `just serve` to start http server. Helpers: `just build-docs`, `just serve-docs` Rebuild docs on file change: `just watch-docs` (requires [entr(1)]) Rebuild docs and run server via one terminal: `just dev-docs` ## Formatting / linting ### ruff The project uses [ruff] to handle formatting, sorting imports and linting. ````{tab} Command uv: ```console $ uv run ruff ``` 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)`]. ```` ## Releasing Since this software is used in production projects, we don't want to release breaking changes. Choose what the next version is. Assuming it's version 0.9.0, it could be: - 0.9.0post0: postrelease, if there was a packaging issue - 0.9.1: bugfix / security / tweak - 0.10.0: breaking changes, new features Let's assume we pick 0.9.1 `CHANGES`: Assure any PRs merged since last release are mentioned. Give a thank you to the contributor. Set the header with the new version and the date. Leave the "current" header and _Insert changes/features/fixes for next release here_ at the top:: current ------- - *Insert changes/features/fixes for next release here* libvcs 0.9.1 (2020-10-12) ------------------------- - :issue:`1`: Fix bug `libvcs/__init__.py` and `__about__.py` - Set version ```console $ git commit -m 'Tag v0.9.1' ``` ```console $ git tag v0.9.1 ``` After `git push` and `git push --tags`, CI will automatically build and deploy to PyPI. ### Releasing (manual) As of 0.10, [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(libvcs): Tag v0.1.1' git tag v0.1.1 git push git push --tags uv build uv publish [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/ --- # Quickstart Source: https://libvcs.git-pull.com/quickstart/ (quickstart)= # Quickstart ## Installation For latest official version: ```console $ pip install --user libvcs ``` Upgrading: ```console $ pip install --user --upgrade libvcs ``` (developmental-releases)= ### Developmental releases New versions of libvcs 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 libvcs ``` - [uv]\: ```console $ uv add libvcs --prerelease allow ``` via trunk (can break easily): - [pip]\: ```console $ pip install --user -e git+https://github.com/vcs-python/libvcs.git#egg=libvcs ``` - [uv]\: ```console $ uv add "git+https://github.com/vcs-python/libvcs.git" ``` [pip]: https://pip.pypa.io/en/stable/ [uv]: https://docs.astral.sh/uv/ ## Basic Usage ### Commands Run git commands directly using {class}`~libvcs.cmd.git.Git`: ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # Initialize a new repository git.init() # Clone a repository git.clone(url='https://github.com/vcs-python/libvcs.git') # Check status git.status() ``` ### Subcommand Managers Work with branches, tags, remotes, and more using the Manager/Cmd pattern: ```python from libvcs.cmd.git import Git git = Git(path='/path/to/repo') # List and filter branches branches = git.branches.ls() remote_branches = git.branches.ls(remotes=True) # Create and manage tags git.tags.create(name='v1.0.0', message='Release 1.0') tag = git.tags.get(tag_name='v1.0.0') # Work with remotes remotes = git.remotes.ls() origin = git.remotes.get(remote_name='origin') origin.prune() ``` See {doc}`/cmd/git/index` for the full API reference. --- # libvcs.sync.base Source: https://libvcs.git-pull.com/sync/base/ # `libvcs.sync.base` Base objects / classes for projects. Adding your own VCS / Extending libvcs can be done through subclassing `BaseSync`. ```{eval-rst} .. automodule:: libvcs.sync.base :members: :show-inheritance: ``` --- # libvcs.sync.git Source: https://libvcs.git-pull.com/sync/git/ # `libvcs.sync.git` For `git(1)`. Compare to: [`fabtools.require.git`](https://fabtools.readthedocs.io/en/0.19.0/api/require/git.html), [`salt.states.git`](https://docs.saltproject.io/en/latest/ref/states/all/salt.states.git.html), [`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html) ```{eval-rst} .. automodule:: libvcs.sync.git :members: :show-inheritance: :undoc-members: ``` --- # libvcs.sync.hg Source: https://libvcs.git-pull.com/sync/hg/ # `libvcs.sync.hg` For mercurial, aka `hg(1)`. ```{eval-rst} .. automodule:: libvcs.sync.hg :members: :show-inheritance: :undoc-members: ``` --- # Sync - libvcs.sync Source: https://libvcs.git-pull.com/sync/ (projects)= # Sync - `libvcs.sync` Compare to: [`fabtools.require.git`](https://fabtools.readthedocs.io/en/0.19.0/api/require/git.html), [`salt.states.git`](https://docs.saltproject.io/en/latest/ref/states/all/salt.states.git.html), [`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html) :::{warning} All APIs are considered experimental and subject to break pre-1.0. They can and will break between versions. ::: ## Modules ::::{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 ```{eval-rst} .. automodule:: libvcs.sync.constants :members: ``` ```{toctree} :hidden: git hg svn base ``` --- # libvcs.sync.svn Source: https://libvcs.git-pull.com/sync/svn/ # `libvcs.sync.svn` For subversion, aka `svn(1)` ```{eval-rst} .. automodule:: libvcs.sync.svn :members: :show-inheritance: :undoc-members: ``` --- # QueryList Filtering Source: https://libvcs.git-pull.com/topics/filtering/ (querylist-filtering)= # QueryList Filtering libvcs uses `QueryList` to enable Django-style filtering on git entities. Every `ls()` method returns a `QueryList`, letting you filter branches, tags, remotes, and more with a fluent, chainable API. ## Basic Filtering The `filter()` method accepts keyword arguments with optional lookup suffixes: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> branches = git.branches.ls() >>> len(branches) >= 1 # At least master branch True ``` ### Exact Match The default lookup is `exact`: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # These are equivalent >>> git.branches.ls().filter(branch_name='master') # doctest: +ELLIPSIS [] >>> git.branches.ls().filter(branch_name__exact='master') # doctest: +ELLIPSIS [] ``` ### Contains and Startswith Use suffixes for partial matching: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # Create branches for this example >>> git.branches.create(branch='feature-docs') '' >>> git.branches.create(branch='feature-tests') '' >>> git.branches.create(branch='bugfix-typo') '' >>> # Branches containing 'feature' >>> feature_branches = git.branches.ls().filter(branch_name__contains='feature') >>> len(feature_branches) >= 2 True >>> # Branches starting with 'bug' >>> bugfix_branches = git.branches.ls().filter(branch_name__startswith='bug') >>> len(bugfix_branches) >= 1 True ``` ## Available Lookups | Lookup | Description | |--------|-------------| | `exact` | Exact match (default) | | `iexact` | Case-insensitive exact match | | `contains` | Substring match | | `icontains` | Case-insensitive substring | | `startswith` | Prefix match | | `istartswith` | Case-insensitive prefix | | `endswith` | Suffix match | | `iendswith` | Case-insensitive suffix | | `in` | Value in list | | `nin` | Value not in list | | `regex` | Regular expression match | | `iregex` | Case-insensitive regex | ## Getting a Single Item Use `get()` to retrieve exactly one matching item: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> branch = git.branches.get(branch_name='master') >>> branch.branch_name 'master' ``` If no match or multiple matches are found, `get()` raises an exception. ## Chaining Filters Filters can be chained for complex queries: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # Create branches for this example >>> git.branches.create(branch='feature-login') '' >>> git.branches.create(branch='feature-signup') '' >>> # Multiple conditions in one filter (AND) >>> git.branches.ls().filter( ... branch_name__startswith='feature', ... branch_name__endswith='signup' ... ) # doctest: +ELLIPSIS [] >>> # Chained filters (also AND) >>> git.branches.ls().filter( ... branch_name__contains='feature' ... ).filter( ... branch_name__contains='login' ... ) # doctest: +ELLIPSIS [] ``` ## Working with Tags The same filtering works on tags: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.tags.create(name='v1.0.0', message='Release 1.0') '' >>> git.tags.create(name='v1.1.0', message='Release 1.1') '' >>> git.tags.create(name='v2.0.0-beta', message='Beta release') '' >>> # Filter tags by version pattern >>> v1_tags = git.tags.ls().filter(tag_name__startswith='v1') >>> len(v1_tags) 2 >>> # Find beta releases >>> beta_tags = git.tags.ls().filter(tag_name__contains='beta') >>> len(beta_tags) 1 ``` ## Regex Filtering For complex patterns, use regex lookups: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # Create tags for regex example >>> git.tags.create(name='v3.0.0', message='Release 3.0') '' >>> git.tags.create(name='v3.1.0', message='Release 3.1') '' >>> # Match semantic version tags >>> results = git.tags.ls().filter(tag_name__regex=r'^v\d+\.\d+\.\d+$') >>> len(results) >= 2 True ``` ## API Reference See {class}`~libvcs._internal.query_list.QueryList` for the complete API. --- # Topics Source: https://libvcs.git-pull.com/topics/ # 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 url_parsing ``` --- # Traversing Git Repos Source: https://libvcs.git-pull.com/topics/traversing_git/ (traversing-git-repos)= # Traversing Git Repos libvcs provides **Managers** and **Commands** for intuitively traversing and navigating entities in a git repository—branches, tags, remotes, stashes, and more—with ORM-like convenience via {class}`~libvcs._internal.query_list.QueryList`. ## Overview The pattern consists of two types of classes: - **Managers** (`git.branches`, `git.tags`, etc.) let you traverse repository entities intuitively, listing, filtering, and retrieving them with ORM-like convenience - **Commands** are contextual ways to run git commands against a specific target entity (e.g., delete a branch, rename a tag, set a remote's URL) ``` Git instance ├── branches: GitBranchManager │ ├── ls() -> QueryList[GitBranchCmd] │ ├── get() -> GitBranchCmd │ └── create() ├── tags: GitTagManager ├── remotes: GitRemoteManager ├── stashes: GitStashManager ├── worktrees: GitWorktreeManager ├── notes: GitNotesManager ├── submodules: GitSubmoduleManager └── reflog: GitReflogManager ``` ## Basic Usage ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) ``` ### Listing Items All Manager classes have an `ls()` method that returns a {class}`~libvcs._internal.query_list.QueryList`: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> branches = git.branches.ls() >>> isinstance(branches, list) True >>> tags = git.tags.ls() >>> remotes = git.remotes.ls() ``` ### Getting a Single Item Use `get()` with filter criteria to retrieve a single item: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.tags.create(name='v1.0.0', message='Release 1.0') '' >>> tag = git.tags.get(tag_name='v1.0.0') >>> tag.tag_name 'v1.0.0' ``` ### Creating Items Manager classes provide `create()` or `add()` methods: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.tags.create(name='v2.0.0', message='Release 2.0') '' >>> git.branches.create(branch='feature-branch') '' ``` ### Per-Entity Operations Cmd objects have methods for mutating or inspecting that entity: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.tags.create(name='v3.0.0', message='Release 3.0') '' >>> tag = git.tags.get(tag_name='v3.0.0') >>> tag.delete() # doctest: +ELLIPSIS "Deleted tag 'v3.0.0' ..." >>> git.branches.create(branch='temp-branch') '' >>> branch = git.branches.get(branch_name='temp-branch') >>> branch.delete() # doctest: +ELLIPSIS 'Deleted branch temp-branch ...' ``` ## Comparison to Raw Commands ### Before: Parsing Strings Without Managers and Commands, you'd parse raw output: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # Raw output requires parsing >>> raw_output = git.run(['tag', '-l']) >>> tag_names = [t for t in raw_output.strip().split('\\n') if t] ``` ### After: Typed Objects With Managers and Commands, you get typed objects: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> tags = git.tags.ls() >>> for tag in tags: # doctest: +SKIP ... print(f"{tag.tag_name}") ``` ## Working with Remotes Add and configure remote repositories: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.remotes.add(name='upstream', url='https://github.com/vcs-python/libvcs.git') '' >>> remotes = git.remotes.ls() >>> len(remotes) >= 1 True ``` Get a remote and update its URL: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.remotes.add(name='backup', url='https://example.com/old.git') '' >>> remote = git.remotes.get(remote_name='backup') >>> remote.remote_name 'backup' >>> remote.set_url(url='https://example.com/new.git') '' ``` ## Branch Operations Beyond creating and deleting, branches support rename and upstream tracking: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.branches.create(branch='old-name') '' >>> branch = git.branches.get(branch_name='old-name') >>> branch.rename('new-name') # doctest: +ELLIPSIS '' ``` Copy a branch: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.branches.create(branch='source-branch') '' >>> branch = git.branches.get(branch_name='source-branch') >>> branch.copy('copied-branch') # doctest: +ELLIPSIS '' ``` ## Stash Workflow Save work in progress and restore it later: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # Push returns message (or "No local changes to save") >>> git.stashes.push(message='WIP: feature work') # doctest: +ELLIPSIS '...' ``` List and inspect stashes: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> stashes = git.stashes.ls() >>> isinstance(stashes, list) True ``` ## Worktree Management Create additional working directories for parallel development: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> worktrees = git.worktrees.ls() >>> len(worktrees) >= 1 # Main worktree always exists True ``` ## Notes Attach metadata to commits: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> git.notes.add(message='Reviewed by Alice') '' ``` ## Filtering with ls() Parameters Manager `ls()` methods accept parameters to narrow results: ```python >>> from libvcs.cmd.git import Git >>> git = Git(path=example_git_repo.path) >>> # All local branches >>> local = git.branches.ls() >>> isinstance(local, list) True >>> # Branches merged into HEAD >>> merged = git.branches.ls(merged='HEAD') >>> isinstance(merged, list) True ``` ## Error Handling When `get()` finds no match, it raises `ObjectDoesNotExist`: ```python >>> from libvcs.cmd.git import Git >>> from libvcs._internal.query_list import ObjectDoesNotExist >>> git = Git(path=example_git_repo.path) >>> try: ... git.branches.get(branch_name='nonexistent-branch-xyz') ... except ObjectDoesNotExist: ... print('Branch not found') Branch not found ``` ## When to Use | Use Case | Approach | |----------|----------| | List/filter/get entities | Manager class (`git.branches.ls()`) | | Mutate a specific entity | Cmd class (`branch.delete()`) | | Run arbitrary git commands | Direct methods (`git.run([...])`) | | Complex pipelines | Mix of both | ## Available Managers | Manager | Access | Operations | |---------|--------|------------| | {class}`~libvcs.cmd.git.GitBranchManager` | `git.branches` | List, create, checkout branches | | {class}`~libvcs.cmd.git.GitTagManager` | `git.tags` | List, create tags | | {class}`~libvcs.cmd.git.GitRemoteManager` | `git.remotes` | List, add, configure remotes | | {class}`~libvcs.cmd.git.GitStashManager` | `git.stashes` | List, push, clear stashes | | {class}`~libvcs.cmd.git.GitWorktreeManager` | `git.worktrees` | List, add, prune worktrees | | {class}`~libvcs.cmd.git.GitNotesManager` | `git.notes` | List, add, prune notes | | {class}`~libvcs.cmd.git.GitSubmoduleManager` | `git.submodules` | List, add, sync submodules | | {class}`~libvcs.cmd.git.GitReflogManager` | `git.reflog` | List, expire reflog entries | See {doc}`/cmd/git/index` for the complete API reference. --- # URL Parsing Source: https://libvcs.git-pull.com/topics/url_parsing/ (url-parsing)= # URL Parsing libvcs provides typed URL parsing for git, Mercurial, and Subversion repositories. Think of it as `urllib.parse` for VCS URLs—detecting URL types, extracting components, and converting between formats. ## Detecting URL Types Use `is_valid()` to check if a string is a valid VCS URL: ```python >>> from libvcs.url.git import GitURL >>> GitURL.is_valid(url='https://github.com/vcs-python/libvcs.git') True >>> GitURL.is_valid(url='git@github.com:vcs-python/libvcs.git') True >>> GitURL.is_valid(url='not-a-url') False ``` ## Parsing URLs Create a URL object to extract components: ```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' >>> url.suffix '.git' ``` ### HTTPS URLs ```python >>> from libvcs.url.git import GitURL >>> url = GitURL(url='https://github.com/vcs-python/libvcs.git') >>> url.scheme 'https' >>> url.hostname 'github.com' >>> url.path 'vcs-python/libvcs' ``` ### SCP-style URLs Git's SCP-style syntax (`user@host:path`) is also supported: ```python >>> from libvcs.url.git import GitURL >>> url = GitURL(url='git@github.com:vcs-python/libvcs.git') >>> url.user 'git' >>> url.hostname 'github.com' ``` ## Converting URL Formats Use `to_url()` to export a URL in a specific format: ```python >>> from libvcs.url.git import GitURL >>> url = GitURL(url='git@github.com:vcs-python/libvcs.git') >>> url.to_url() 'git@github.com:vcs-python/libvcs.git' ``` ## Pip-style URLs libvcs handles pip-style VCS URLs with branch/tag specifiers: ```python >>> from libvcs.url.git import GitURL >>> url = GitURL(url='git+https://github.com/django/django.git@main') >>> url.scheme 'git+https' >>> url.rev 'main' ``` ## Other VCS Types ### Mercurial ```python >>> from libvcs.url.hg import HgURL >>> HgURL.is_valid(url='https://hg.mozilla.org/mozilla-central') True >>> url = HgURL(url='https://hg.mozilla.org/mozilla-central') >>> url.hostname 'hg.mozilla.org' ``` ### Subversion ```python >>> from libvcs.url.svn import SvnURL >>> SvnURL.is_valid(url='svn+ssh://svn.example.org/repo/trunk') True >>> url = SvnURL(url='svn+ssh://svn.example.org/repo/trunk') >>> url.scheme 'svn+ssh' ``` ## URL Registry The registry can auto-detect VCS type from a URL: ```python >>> from libvcs.url.registry import registry >>> matches = registry.match('git@github.com:vcs-python/libvcs.git') >>> len(matches) >= 1 True ``` ## API Reference See {doc}`/url/index` for the complete API reference. --- # Framework: Add and extend URL parsers - libvcs.url.base Source: https://libvcs.git-pull.com/url/base/ (parser-framework)= # Framework: Add and extend URL parsers - `libvcs.url.base` ```{eval-rst} .. automodule:: libvcs.url.base :members: :undoc-members: ``` --- # Constants - libvcs.url.constants Source: https://libvcs.git-pull.com/url/constants/ --- myst: html_meta: "property=og:locale": "en_US" --- (url-parser-constants)= # Constants - `libvcs.url.constants` ```{eval-rst} .. automodule:: libvcs.url.constants :members: :undoc-members: ``` --- # Git URL Parser - libvcs.url.git Source: https://libvcs.git-pull.com/url/git/ --- myst: html_meta: "description lang=en": "Parse git URLs in python" "keywords": "libvcs, git, parse, urls" "property=og:locale": "en_US" --- # Git URL Parser - `libvcs.url.git` Detect, parse, and change git URLs using libvcs's URL parser for `git(1)`. It builds on top of the {ref}`VCS-friendly URL parser framework `. ```{eval-rst} .. automodule:: libvcs.url.git :members: :undoc-members: :show-inheritance: ``` --- # Mercurial URL Parser - libvcs.url.hg Source: https://libvcs.git-pull.com/url/hg/ # Mercurial URL Parser - `libvcs.url.hg` For hg, aka `hg(1)`. ```{eval-rst} .. automodule:: libvcs.url.hg :members: :undoc-members: ``` --- # URL Parser - libvcs.url Source: https://libvcs.git-pull.com/url/ (parse)= # URL Parser - `libvcs.url` We all love {mod}`urllib.parse`, but what about VCS systems? Also, things like completions and typings being in demand, what of all these factories? Good python code, but how to we get editor support and the nice satisfaction of types snapping together? If there was a type-friendly structure - like writing our own abstract base class - or a {mod}`dataclasses` - while also being extensible to patterns and groupings, maybe we could strike a 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 {meth}`libvcs.url.git.GitURL.is_valid()` ```python >>> from libvcs.url.git import GitURL >>> GitURL.is_valid(url='https://github.com/vcs-python/libvcs.git') True ``` ```python >>> from libvcs.url.git import GitURL >>> GitURL.is_valid(url='git@github.com:vcs-python/libvcs.git') True ``` ```` ````{tab} hg {meth}`libvcs.url.hg.HgURL.is_valid()` ```python >>> from libvcs.url.hg import HgURL >>> HgURL.is_valid(url='https://hg.mozilla.org/mozilla-central/mozilla-central') True ``` ```python >>> from libvcs.url.hg import HgURL >>> HgURL.is_valid(url='hg@hg.mozilla.org:MyProject/project') True ``` ```` ````{tab} svn {meth}`libvcs.url.svn.SvnURL.is_valid()` ```python >>> from libvcs.url.svn import SvnURL >>> SvnURL.is_valid( ... url='https://svn.project.org/project-central/project-central') True ``` ```python >>> from libvcs.url.svn import SvnURL >>> SvnURL.is_valid(url='svn@svn.project.org:MyProject/project') True ``` ```` ## Parse VCS URLs _Compare to {class}`urllib.parse.ParseResult`_ ````{tab} git {class}`libvcs.url.git.GitURL` ```python >>> from libvcs.url.git import GitURL >>> GitURL(url='git@github.com:vcs-python/libvcs.git') GitURL(url=git@github.com:vcs-python/libvcs.git, user=git, hostname=github.com, path=vcs-python/libvcs, suffix=.git, rule=core-git-scp) ``` ```` ````{tab} hg {class}`libvcs.url.hg.HgURL` ```python >>> from libvcs.url.hg import HgURL >>> HgURL( ... url="http://hugin.hg.sourceforge.net:8000/hgroot/hugin/hugin") HgURL(url=http://hugin.hg.sourceforge.net:8000/hgroot/hugin/hugin, scheme=http, hostname=hugin.hg.sourceforge.net, port=8000, path=hgroot/hugin/hugin, rule=core-hg) ``` ```` ````{tab} svn {class}`libvcs.url.svn.SvnURL` ```python >>> from libvcs.url.svn import SvnURL >>> SvnURL( ... url='svn+ssh://svn.debian.org/svn/aliothproj/path/in/project/repository') SvnURL(url=svn+ssh://svn.debian.org/svn/aliothproj/path/in/project/repository, scheme=svn+ssh, hostname=svn.debian.org, path=svn/aliothproj/path/in/project/repository, rule=pip-url) ``` ```` ## Export usable URLs - git: {meth}`libvcs.url.git.GitURL.to_url()` - hg: {meth}`libvcs.url.hg.HgURL.to_url()` - svn: {meth}`libvcs.url.svn.SvnURL.to_url()` `pip` knows what a certain URL string means, but `git clone` won't. e.g. `pip install git+https://github.com/django/django.git@3.2` works great with `pip`. ```console $ pip install git+https://github.com/django/django.git@3.2 ... Successfully installed Django-3.2 ``` but `git clone` can't use that: ```console $ git clone git+https://github.com/django/django.git@3.2 # Fail ... Cloning into django.git@3.2''...' git: 'remote-git+https' is not a git command. See 'git --help'. ``` It needs something like this: ```console $ git clone https://github.com/django/django.git --branch 3.2 ``` But before we get there, we don't know if we want a URL yet. We return a structure, e.g. `GitURL`. - Common result primitives across VCS, e.g. `GitURL`. Compare to a {class}`urllib.parse.ParseResult` in `urlparse` This is where fun can happen, or you can just parse a URL. - Allow mutating / replacing parse of a vcs (e.g. just the hostname) - Support common cases with popular VCS systems - Support extending parsing for users needing to do so ## Scope ### Out of the box The ambition for this is to build extendable parsers for package-like URLs, e.g. - Vanilla VCS URLs - any URL supported by the VCS binary, e.g. `git(1)`, `svn(1)`, `hg(1)`. - [pip]-style urls [^pip-url] - branches - tags - [NPM]-style urls[^npm-url] - branches - tags [pip]: https://pip.pypa.io/en/stable/ [^pip-url]: PIP-style URLs https://pip.pypa.io/en/stable/topics/vcs-support/ [npm]: https://docs.npmjs.com/ [^npm-url]: NPM style URLs https://docs.npmjs.com/about-packages-and-modules#npm-package-git-url-formats ## Extendability Patterns can be registered. [Similar behavior](https://stackoverflow.com/a/6264214/1396928) exists in {mod}`urlparse` (undocumented). - Any formats not covered by the stock - Custom urls - For orgs on , e.g: - `python:mypy` -> `git@github.com:python/mypy.git` - `inkscape:inkscape` -> `git@gitlab.com:inkscape/inkscape.git` - For out of domain trackers, e.g. Direct to site: - `cb:python-vcs/libtmux` -> `https://codeberg.org/vcs-python/libvcs` - `kde:plasma/plasma-sdk` -> `git@invent.kde.org:plasma/plasma-sdk.git` Aside: Note [KDE's git docs] use of [`url..insteadOf`] and [`url..pushInsteadOf`] Direct to site + org / group: - `gnome:gedit` -> `git@gitlab.gnome.org:GNOME/gedit.git` - `openstack:openstack` -> `https://opendev.org/openstack/openstack.git` - `mozilla:central` -> `https://hg.mozilla.org/mozilla-central/` [kde's git docs]: https://community.kde.org/Infrastructure/Git#Pushing [`url..insteadof`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf [`url..pushinsteadof`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtpushInsteadOf 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 [vcspull configuration](https://vcspull.git-pull.com/), will be able to detect and accept various URL patterns. ### Matchers: Defaults When a match occurs, its `defaults` will fill in non-matched groups. ### Matchers: First wins When registering new matchers, higher `weight`s are checked first. If it's a valid regex grouping, it will be picked. ```{toctree} :hidden: git svn hg base registry constants ``` --- # VCS Detection - libvcs.url.registry Source: https://libvcs.git-pull.com/url/registry/ # VCS Detection - `libvcs.url.registry` Detect VCS from `git`, `hg`, and `svn` URLs. **Basic example:** ```python >>> from libvcs.url.registry import registry, ParserMatch >>> from libvcs.url.git import GitURL >>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git') [ParserMatch(vcs='git', match=GitURL(...))] >>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git', is_explicit=True) [ParserMatch(vcs='git', match=GitURL(...))] >>> registry.match('git+ssh://git@invent.kde.org:plasma/plasma-sdk.git') [ParserMatch(vcs='git', match=GitURL(...))] >>> registry.match('git+ssh://git@invent.kde.org:plasma/plasma-sdk.git', is_explicit=False) [] >>> registry.match('git+ssh://git@invent.kde.org:plasma/plasma-sdk.git', is_explicit=True) [ParserMatch(vcs='git', match=GitURL(...))] ``` **From the ground up:** ```python >>> import dataclasses >>> from libvcs.url.base import Rule, RuleMap >>> from libvcs.url.registry import ParserMatch, VCSRegistry >>> from libvcs.url.git import GitURL This will match `github:org/repo`: >>> class GitHubPrefix(Rule): ... label = 'gh-prefix' ... description ='Matches prefixes like github:org/repo' ... pattern = r'^github:(?P.*)$' ... defaults = { ... 'hostname': 'github.com', ... 'scheme': 'https' ... } ... is_explicit = True # We know it's git, not any other VCS ... weight = 100 Prefix for KDE infrastructure, `kde:group/repository`: >>> class KDEPrefix(Rule): # https://community.kde.org/Infrastructure/Git ... label = 'kde-prefix' ... description ='Matches prefixes like kde:org/repo' ... pattern = r'^kde:(?P\w[^:]+)$' ... defaults = { ... 'hostname': 'invent.kde.org', ... 'scheme': 'https' ... } ... is_explicit = True ... weight = 100 >>> @dataclasses.dataclass(repr=False) ... class MyGitURLParser(GitURL): ... rule_map = RuleMap( ... _rule_map={ ... **GitURL.rule_map._rule_map, ... 'github_prefix': GitHubPrefix, ... 'kde_prefix': KDEPrefix, ... } ... ) >>> my_parsers: "ParserLazyMap" = { ... "git": MyGitURLParser, ... "hg": "libvcs.url.hg.HgURL", ... "svn": "libvcs.url.svn.SvnURL", ... } >>> vcs_matcher = VCSRegistry(parsers=my_parsers) >>> vcs_matcher.match('git@invent.kde.org:plasma/plasma-sdk.git') [ParserMatch(vcs='git', match=MyGitURLParser(...)), ParserMatch(vcs='hg', match=HgURL(...)), ParserMatch(vcs='svn', match=SvnURL(...))] Still works with everything GitURL does: >>> vcs_matcher.match('git+ssh://git@invent.kde.org:plasma/plasma-sdk.git', is_explicit=True) [ParserMatch(vcs='git', match=MyGitURLParser(...))] >>> vcs_matcher.match('github:webpack/webpack', is_explicit=True) [ParserMatch(vcs='git', match=MyGitURLParser(url=github:webpack/webpack, scheme=https, hostname=github.com, path=webpack/webpack, rule=gh-prefix))] >>> git_match = vcs_matcher.match('github:webpack/webpack', is_explicit=True)[0].match >>> git_match.to_url() 'https://github.com/webpack/webpack' If an ssh URL is preferred: >>> git_match.scheme = None >>> git_match.to_url() 'git@github.com:webpack/webpack' >>> vcs_matcher.match('kde:frameworks/kirigami', is_explicit=True) [ParserMatch(vcs='git', match=MyGitURLParser(url=kde:frameworks/kirigami, scheme=https, hostname=invent.kde.org, path=frameworks/kirigami, rule=kde-prefix))] >>> kde_match = vcs_matcher.match('kde:frameworks/kirigami', is_explicit=True)[0].match >>> kde_match MyGitURLParser(url=kde:frameworks/kirigami, scheme=https, hostname=invent.kde.org, path=frameworks/kirigami, rule=kde-prefix) >>> kde_match.to_url() 'https://invent.kde.org/frameworks/kirigami' >>> kde_match.scheme = None >>> kde_match.to_url() 'git@invent.kde.org:frameworks/kirigami' ``` ```{eval-rst} .. automodule:: libvcs.url.registry :members: :undoc-members: ``` --- # SVN URL Parser - libvcs.url.svn Source: https://libvcs.git-pull.com/url/svn/ # SVN URL Parser - `libvcs.url.svn` For svn, aka `svn(1)`. ```{eval-rst} .. automodule:: libvcs.url.svn :members: :undoc-members: ``` ---