From c517d82f349bfb2bb6a3c1e20b17a78908d1ec82 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 1 Jul 2026 22:50:16 -0500 Subject: [PATCH 01/10] docs(examples): Convert examples to doctests why: Non-running ```python blocks with placeholder repo paths dodged pytest execution and hid API errors that raise TypeError or return empty results when readers copy them. what: - Convert docs/cmd/git/* Example blocks to fixture-backed doctests (example_git_repo, tmp_path), with prose replacing inline comments - Convert quickstart.md, index.md "At a glance", and internals/query_list.md blocks the same way - Fix API errors the dead blocks hid: branches.create() called positionally (keyword-only branch=), notes.add/get object= (real name object_sha), reflog.ls(ref=) and expire(ref=) (no such params), remote set_url() called positionally (keyword-only url=), worktrees.add(branch=) (no such param), QueryList filter examples using nonexistent attributes (name, is_remote, commit__sha) - Replace forbidden `# doctest: +SKIP` in traversing_git.md with a deterministic running loop --- docs/cmd/git/branch.md | 48 +++++++++++++++------ docs/cmd/git/index.md | 35 ++++++++------- docs/cmd/git/notes.md | 47 +++++++++++++------- docs/cmd/git/reflog.md | 37 ++++++++++------ docs/cmd/git/remote.md | 38 +++++++++++------ docs/cmd/git/stash.md | 37 ++++++++-------- docs/cmd/git/submodule.md | 35 +++++++-------- docs/cmd/git/tag.md | 41 +++++++++++------- docs/cmd/git/worktree.md | 38 +++++++++-------- docs/index.md | 16 +++---- docs/internals/query_list.md | 80 +++++++++++++++++++++++------------ docs/quickstart.md | 59 ++++++++++++++------------ docs/topics/traversing_git.md | 8 ++-- 13 files changed, 312 insertions(+), 207 deletions(-) diff --git a/docs/cmd/git/branch.md b/docs/cmd/git/branch.md index cc71877dc..db1cdb5db 100644 --- a/docs/cmd/git/branch.md +++ b/docs/cmd/git/branch.md @@ -7,26 +7,46 @@ For `git-branch(1)`. Manage git branches using {class}`~libvcs.cmd.git.GitBranchManager` (collection-level) and {class}`~libvcs.cmd.git.GitBranchCmd` (per-branch operations). -### Example +### Examples -```python -from libvcs.cmd.git import Git +List branches — local by default, remote with `remotes=True`: -git = Git(path='/path/to/repo') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> branches = git.branches.ls() +>>> len(branches) >= 1 +True +>>> remote_branches = git.branches.ls(remotes=True) +>>> isinstance(remote_branches, list) +True +``` -# List all branches -branches = git.branches.ls() +Create a branch, then look it up: -# List remote branches only -remote_branches = git.branches.ls(remotes=True) +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.branches.create(branch='feature-branch') +'' +>>> branch = git.branches.get(branch_name='feature-branch') +>>> branch.branch_name +'feature-branch' +``` -# Create a new branch -git.branches.create('feature-branch') +Rename or delete a branch through its Cmd object: -# Get a specific branch and operate on it -branch = git.branches.get(branch_name='feature-branch') -branch.rename('new-feature') -branch.delete() +```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-feature') +'' +>>> renamed = git.branches.get(branch_name='new-feature') +>>> renamed.delete() # doctest: +ELLIPSIS +'Deleted branch new-feature ...' ``` ## API Reference diff --git a/docs/cmd/git/index.md b/docs/cmd/git/index.md index c48a27fd6..8af75deec 100644 --- a/docs/cmd/git/index.md +++ b/docs/cmd/git/index.md @@ -31,25 +31,24 @@ Git instance ### Quick Example -```python -from libvcs.cmd.git import Git - -git = Git(path='/path/to/repo') - -# List all branches -branches = git.branches.ls() +List branches, rename one through its Cmd object, and manage tags: -# 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() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> branches = git.branches.ls() +>>> len(branches) >= 1 +True +>>> git.branches.create(branch='old-name') +'' +>>> branch = git.branches.get(branch_name='old-name') +>>> branch.rename('new-name') +'' +>>> git.tags.create(name='v1.0.0', message='Release 1.0') +'' +>>> tag = git.tags.get(tag_name='v1.0.0') +>>> tag.delete() # doctest: +ELLIPSIS +"Deleted tag 'v1.0.0' ..." ``` ```{toctree} diff --git a/docs/cmd/git/notes.md b/docs/cmd/git/notes.md index 01b545563..073ebaf03 100644 --- a/docs/cmd/git/notes.md +++ b/docs/cmd/git/notes.md @@ -7,27 +7,44 @@ For `git-notes(1)`. Manage git notes using {class}`~libvcs.cmd.git.GitNotesManager` (collection-level) and {class}`~libvcs.cmd.git.GitNoteCmd` (per-note operations). -### Example +### Examples -```python -from libvcs.cmd.git import Git +Add a note to the current commit (`object_sha` defaults to `HEAD`), then list +notes: -git = Git(path='/path/to/repo') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.notes.add(message='This is a note') +'' +>>> notes = git.notes.ls() +>>> len(notes) >= 1 +True +``` -# Add a note to a commit -git.notes.add(object='HEAD', message='This is a note') +Operate on a note through its Cmd object — show it, append to it, remove it: -# List all notes -notes = git.notes.ls() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.notes.add(message='Reviewed by Alice') +'' +>>> note = git.notes.ls()[0] +>>> note.show() +'Reviewed by Alice\n' +>>> note.append(message='Additional info') +'' +>>> note.remove() # doctest: +ELLIPSIS +'...' +``` -# 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 attached to objects that no longer exist: -# Prune notes for non-existent objects -git.notes.prune() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.notes.prune() +'' ``` ## API Reference diff --git a/docs/cmd/git/reflog.md b/docs/cmd/git/reflog.md index 59f52162c..75608db01 100644 --- a/docs/cmd/git/reflog.md +++ b/docs/cmd/git/reflog.md @@ -7,24 +7,37 @@ For `git-reflog(1)`. Manage git reflog using {class}`~libvcs.cmd.git.GitReflogManager` (collection-level) and {class}`~libvcs.cmd.git.GitReflogEntryCmd` (per-entry operations). -### Example +### Examples -```python -from libvcs.cmd.git import Git +List reflog entries and look one up by refspec: -git = Git(path='/path/to/repo') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> entries = git.reflog.ls() +>>> len(entries) >= 1 +True +>>> entry = git.reflog.get(refspec='HEAD@{0}') +>>> entry.refspec +'HEAD@{0}' +``` -# List reflog entries -entries = git.reflog.ls() +Check whether a ref has a reflog: -# List entries for a specific ref -head_entries = git.reflog.ls(ref='HEAD') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.reflog.exists('HEAD') +True +``` -# Check if reflog exists for a ref -git.reflog.exists(ref='main') +Expire old reflog entries: -# Expire old reflog entries -git.reflog.expire(ref='HEAD', expire='90.days.ago') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.reflog.expire(expire='90.days.ago') +'' ``` ## API Reference diff --git a/docs/cmd/git/remote.md b/docs/cmd/git/remote.md index be42a9b1c..b4825e2e0 100644 --- a/docs/cmd/git/remote.md +++ b/docs/cmd/git/remote.md @@ -7,24 +7,38 @@ For `git-remote(1)`. Manage git remotes using {class}`~libvcs.cmd.git.GitRemoteManager` (collection-level) and {class}`~libvcs.cmd.git.GitRemoteCmd` (per-remote operations). -### Example +### Examples + +List remotes — a freshly cloned repository has its `origin`: ```python -from libvcs.cmd.git import Git +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.remotes.ls() # doctest: +ELLIPSIS +[] +``` -git = Git(path='/path/to/repo') +Add a remote: -# List all remotes -remotes = git.remotes.ls() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.remotes.add(name='upstream', url='https://github.com/org/repo.git') +'' +``` -# Add a new remote -git.remotes.add(name='upstream', url='https://github.com/org/repo.git') +Get a specific remote and operate on it through its Cmd object: -# 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') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> origin = git.remotes.get(remote_name='origin') +>>> 'origin' in origin.show() +True +>>> origin.prune() +'' +>>> origin.set_url(url='https://example.com/new.git') +'' ``` ## API Reference diff --git a/docs/cmd/git/stash.md b/docs/cmd/git/stash.md index 82b471fb0..f4fe4ec71 100644 --- a/docs/cmd/git/stash.md +++ b/docs/cmd/git/stash.md @@ -12,27 +12,26 @@ and {class}`~libvcs.cmd.git.GitStashEntryCmd` (per-stash operations). ({class}`~libvcs.cmd.git.GitStashManager`) for the new Manager/Cmd pattern. ::: -### Example +### Examples -```python -from libvcs.cmd.git import Git - -git = Git(path='/path/to/repo') - -# Push changes to stash -git.stashes.push(message='Work in progress') +Stash work in progress, inspect it, restore it, then clear the stash list: -# 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() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> _ = (example_git_repo.path / 'testfile.test').write_text('wip', encoding='utf-8') +>>> git.stashes.push(message='Work in progress') # doctest: +ELLIPSIS +'Saved working directory and index state ...' +>>> stashes = git.stashes.ls() +>>> len(stashes) +1 +>>> stash = git.stashes.get(index=0) +>>> stash.show() # doctest: +ELLIPSIS +'...testfile.test...' +>>> stash.apply() # doctest: +ELLIPSIS +'...' +>>> git.stashes.clear() +'' ``` ## API Reference diff --git a/docs/cmd/git/submodule.md b/docs/cmd/git/submodule.md index 83110ca6a..58eedbf7d 100644 --- a/docs/cmd/git/submodule.md +++ b/docs/cmd/git/submodule.md @@ -12,29 +12,26 @@ and {class}`~libvcs.cmd.git.GitSubmoduleEntryCmd` (per-submodule operations). ({class}`~libvcs.cmd.git.GitSubmoduleManager`) for the new Manager/Cmd pattern. ::: -### Example +### Examples -```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() +List submodules — a repository without any simply returns an empty list — and +sync submodule URLs into `.git/config`: -# 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() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.submodules.ls() +[] +>>> git.submodules.sync() +'' ``` +Register a submodule with {meth}`~libvcs.cmd.git.GitSubmoduleManager.add`, +then initialize and fetch it through the entry's +{meth}`~libvcs.cmd.git.GitSubmoduleEntryCmd.init` and +{meth}`~libvcs.cmd.git.GitSubmoduleEntryCmd.update` — each method's API +reference below carries a runnable example. + ## API Reference ```{eval-rst} diff --git a/docs/cmd/git/tag.md b/docs/cmd/git/tag.md index d18324ddd..b4f810906 100644 --- a/docs/cmd/git/tag.md +++ b/docs/cmd/git/tag.md @@ -7,26 +7,35 @@ For `git-tag(1)`. Manage git tags using {class}`~libvcs.cmd.git.GitTagManager` (collection-level) and {class}`~libvcs.cmd.git.GitTagCmd` (per-tag operations). -### Example +### Examples -```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') +Create a tag, then list tags — optionally narrowed to a pattern: -# List all tags -tags = git.tags.ls() +```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.0') +'' +>>> tags = git.tags.ls() +>>> len(tags) >= 1 +True +>>> release_tags = git.tags.ls(pattern='v*') # doctest: +ELLIPSIS +>>> release_tags[0].tag_name +'v1.0.0' +``` -# Filter tags -release_tags = git.tags.ls(pattern='v*') +Get a specific tag and operate on it through its Cmd object: -# Get a specific tag and operate on it -tag = git.tags.get(tag_name='v1.0.0') -tag.show() -tag.delete() +```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.0') +'' +>>> tag = git.tags.get(tag_name='v2.0.0') +>>> tag.show() # doctest: +ELLIPSIS +'tag v2.0.0...' +>>> tag.delete() # doctest: +ELLIPSIS +"Deleted tag 'v2.0.0' ..." ``` ## API Reference diff --git a/docs/cmd/git/worktree.md b/docs/cmd/git/worktree.md index 2c42c24ab..732f7eacb 100644 --- a/docs/cmd/git/worktree.md +++ b/docs/cmd/git/worktree.md @@ -7,27 +7,31 @@ For `git-worktree(1)`. Manage git worktrees using {class}`~libvcs.cmd.git.GitWorktreeManager` (collection-level) and {class}`~libvcs.cmd.git.GitWorktreeCmd` (per-worktree operations). -### Example +### Examples -```python -from libvcs.cmd.git import Git - -git = Git(path='/path/to/repo') - -# List all worktrees -worktrees = git.worktrees.ls() +Add a worktree checked out at `HEAD`, then look it up — the main worktree +always exists, so the list grows to two: -# Add a new worktree -git.worktrees.add(path='/path/to/worktree', branch='feature-branch') +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.worktrees.add(path=tmp_path / 'example-worktree', commit_ish='HEAD') # doctest: +ELLIPSIS +'HEAD is now at ...' +>>> worktrees = git.worktrees.ls() +>>> len(worktrees) >= 2 +True +>>> wt = git.worktrees.get(worktree_path=worktrees[0].worktree_path) +>>> wt.worktree_path == worktrees[0].worktree_path +True +``` -# 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 worktree metadata: -# Prune stale worktrees -git.worktrees.prune() +```python +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.worktrees.prune() +'' ``` ## API Reference diff --git a/docs/index.md b/docs/index.md index 8f42aece7..85589c3c9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -66,14 +66,14 @@ 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 +>>> 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 diff --git a/docs/internals/query_list.md b/docs/internals/query_list.md index d68bc59a8..961ef4421 100644 --- a/docs/internals/query_list.md +++ b/docs/internals/query_list.md @@ -5,34 +5,49 @@ libvcs returns a `QueryList`, enabling chainable filtering on the results. ## How It's Used -All Manager classes return `QueryList` from their `ls()` methods: +All Manager classes return `QueryList` from their `ls()` methods — +`git.branches.ls()` yields `QueryList[GitBranchCmd]`, `git.tags.ls()` yields +`QueryList[GitTagCmd]`, and so on: ```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] +>>> from libvcs.cmd.git import Git +>>> from libvcs._internal.query_list import QueryList +>>> git = Git(path=example_git_repo.path) +>>> isinstance(git.branches.ls(), QueryList) +True +>>> isinstance(git.tags.ls(), QueryList) +True +>>> isinstance(git.remotes.ls(), QueryList) +True ``` ## Filtering -`QueryList` extends Python's built-in `list` with Django-style lookups: +`QueryList` extends Python's built-in `list` with Django-style lookups. +Filter on any attribute of the contained objects — exact by default, or with +a lookup suffix: ```python -# Exact match -branches.filter(name='main') +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.branches.create(branch='feature-a') +'' +>>> git.branches.ls().filter(branch_name='master') # doctest: +ELLIPSIS +[] +>>> git.branches.ls().filter(branch_name__icontains='FEATURE') # doctest: +ELLIPSIS +[] +``` -# Case-insensitive contains -branches.filter(name__icontains='feature') +Lookups traverse nested structures with `__` as well: -# Nested attribute access -branches.filter(commit__sha__startswith='abc123') +```python +>>> from libvcs._internal.query_list import QueryList +>>> cities = QueryList([ +... {'city': 'Tampa', 'weather': {'sky': 'sunny'}}, +... {'city': 'Chicago', 'weather': {'sky': 'windy'}}, +... ]) +>>> cities.filter(weather__sky='sunny') +[{'city': 'Tampa', 'weather': {'sky': 'sunny'}}] ``` ### Available Lookups @@ -54,17 +69,28 @@ branches.filter(commit__sha__startswith='abc123') ### Chaining -Filters can be chained and combined: +Filters can be chained and combined — multiple conditions in one call AND +together, and `get()` retrieves exactly one match: ```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') +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> git.branches.create(branch='feature-login') +'' +>>> git.branches.create(branch='feature-signup') +'' +>>> git.branches.ls().filter( +... branch_name__startswith='feature', +... branch_name__endswith='login', +... ) # doctest: +ELLIPSIS +[] +>>> git.branches.ls().filter( +... branch_name__contains='feature' +... ).filter(branch_name__contains='signup') # doctest: +ELLIPSIS +[] +>>> branch = git.branches.get(branch_name='master') +>>> branch.branch_name +'master' ``` ## API Reference diff --git a/docs/quickstart.md b/docs/quickstart.md index 109beb5df..73c2488da 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -57,21 +57,29 @@ via trunk (can break easily): ### Commands -Run git commands directly using {class}`~libvcs.cmd.git.Git`: +Run git commands directly using {class}`~libvcs.cmd.git.Git`. Initialize a +new repository: ```python -from libvcs.cmd.git import Git - -git = Git(path='/path/to/repo') - -# Initialize a new repository -git.init() +>>> from libvcs.cmd.git import Git +>>> repo_path = tmp_path / 'example' +>>> repo_path.mkdir() +>>> git = Git(path=repo_path) +>>> git.init() # doctest: +ELLIPSIS +'Initialized empty Git repository in ...' +``` -# Clone a repository -git.clone(url='https://github.com/vcs-python/libvcs.git') +Clone an existing repository and check its status: -# Check status -git.status() +```python +>>> from libvcs.cmd.git import Git +>>> clone_path = tmp_path / 'clone' +>>> clone_path.mkdir() +>>> git = Git(path=clone_path) +>>> git.clone(url=f'file://{create_git_remote_repo()}') +'' +>>> git.status() # doctest: +ELLIPSIS +"On branch master..." ``` ### Subcommand Managers @@ -79,22 +87,19 @@ git.status() 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() +>>> from libvcs.cmd.git import Git +>>> git = Git(path=example_git_repo.path) +>>> branches = git.branches.ls() +>>> len(branches) >= 1 +True +>>> 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' +>>> origin = git.remotes.get(remote_name='origin') +>>> origin.prune() +'' ``` See {doc}`/cmd/git/index` for the full API reference. diff --git a/docs/topics/traversing_git.md b/docs/topics/traversing_git.md index 01b755714..66ec80494 100644 --- a/docs/topics/traversing_git.md +++ b/docs/topics/traversing_git.md @@ -120,9 +120,11 @@ 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}") +>>> git.tags.create(name='v1.0.0', message='Release 1.0') +'' +>>> for tag in git.tags.ls(): +... print(tag.tag_name) +v1.0.0 ``` ## Working with Remotes From b877ed123abde7a9802aa923cc070b0107251503 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 1 Jul 2026 22:54:10 -0500 Subject: [PATCH 02/10] docs(links): Link first prose mentions of API objects why: docs/AGENTS.md requires the first prose mention of any symbol with a destination to carry the most specific MyST role; most pages predate the guide and left objects, methods, and exceptions as plain backticks or bare text. what: - Add {class}/{meth}/{exc}/{mod} roles for QueryList, filter(), get(), ObjectDoesNotExist, MultipleObjectsReturned, GitURL, HgURL, SvnURL, to_url(), is_valid(), BaseSync, urllib.parse, list - Link the Manager/Cmd pattern to traversing-git-repos wherever pages mention it (quickstart, cmd hubs, stash/submodule notes, query_list internals) - Replace .md/path links with {ref}/{doc} roles (index, contributing) - Link man-page references across docs/cmd/git/ and url/git.md uniformly to git-scm.com - Link external projects at first mention: Django, pip VCS support, pytest-xdist, URL registry doc page --- docs/api/pytest-plugin.md | 2 +- docs/cmd/git/branch.md | 2 +- docs/cmd/git/index.md | 7 +++++-- docs/cmd/git/notes.md | 2 +- docs/cmd/git/reflog.md | 2 +- docs/cmd/git/remote.md | 2 +- docs/cmd/git/stash.md | 5 +++-- docs/cmd/git/submodule.md | 5 +++-- docs/cmd/git/tag.md | 2 +- docs/cmd/git/worktree.md | 2 +- docs/cmd/index.md | 6 +++--- docs/index.md | 4 ++-- docs/internals/query_list.md | 7 +++++-- docs/project/contributing.md | 2 +- docs/quickstart.md | 3 ++- docs/sync/base.md | 3 ++- docs/topics/filtering.md | 14 ++++++++++---- docs/topics/traversing_git.md | 3 ++- docs/topics/url_parsing.md | 22 +++++++++++++++------- docs/url/git.md | 3 ++- 20 files changed, 62 insertions(+), 36 deletions(-) diff --git a/docs/api/pytest-plugin.md b/docs/api/pytest-plugin.md index 8b91b5230..75456ba3f 100644 --- a/docs/api/pytest-plugin.md +++ b/docs/api/pytest-plugin.md @@ -47,7 +47,7 @@ def setup( its own clone. The remote is built once and cached for the session, then copied for every consumer, so a test can commit, add remotes, or rewrite history without affecting any other test — and the fixtures stay safe under parallel -runs (`pytest-xdist`). +runs ([pytest-xdist](https://pytest-xdist.readthedocs.io/)). ::: ## Types diff --git a/docs/cmd/git/branch.md b/docs/cmd/git/branch.md index db1cdb5db..09120302b 100644 --- a/docs/cmd/git/branch.md +++ b/docs/cmd/git/branch.md @@ -1,6 +1,6 @@ # `branch` -For `git-branch(1)`. +For [`git-branch(1)`](https://git-scm.com/docs/git-branch). ## Overview diff --git a/docs/cmd/git/index.md b/docs/cmd/git/index.md index 8af75deec..1439ff80e 100644 --- a/docs/cmd/git/index.md +++ b/docs/cmd/git/index.md @@ -1,6 +1,6 @@ # `libvcs.cmd.git` -For `git(1)`. +For [`git(1)`](https://git-scm.com/docs/git). _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), @@ -11,9 +11,12 @@ _Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git. 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 + entities intuitively with ORM-like filtering via + {class}`~libvcs._internal.query_list.QueryList` - **Commands** are contextual ways to run git commands against a specific target entity +See {ref}`traversing-git-repos` for the full guide. + ``` Git instance ├── branches: GitBranchManager diff --git a/docs/cmd/git/notes.md b/docs/cmd/git/notes.md index 073ebaf03..bc32c2a7a 100644 --- a/docs/cmd/git/notes.md +++ b/docs/cmd/git/notes.md @@ -1,6 +1,6 @@ # `notes` -For `git-notes(1)`. +For [`git-notes(1)`](https://git-scm.com/docs/git-notes). ## Overview diff --git a/docs/cmd/git/reflog.md b/docs/cmd/git/reflog.md index 75608db01..ca8e34de1 100644 --- a/docs/cmd/git/reflog.md +++ b/docs/cmd/git/reflog.md @@ -1,6 +1,6 @@ # `reflog` -For `git-reflog(1)`. +For [`git-reflog(1)`](https://git-scm.com/docs/git-reflog). ## Overview diff --git a/docs/cmd/git/remote.md b/docs/cmd/git/remote.md index b4825e2e0..7853ca04e 100644 --- a/docs/cmd/git/remote.md +++ b/docs/cmd/git/remote.md @@ -1,6 +1,6 @@ # `remote` -For `git-remote(1)`. +For [`git-remote(1)`](https://git-scm.com/docs/git-remote). ## Overview diff --git a/docs/cmd/git/stash.md b/docs/cmd/git/stash.md index f4fe4ec71..a3941e51a 100644 --- a/docs/cmd/git/stash.md +++ b/docs/cmd/git/stash.md @@ -1,6 +1,6 @@ # `stash` -For `git-stash(1)`. +For [`git-stash(1)`](https://git-scm.com/docs/git-stash). ## Overview @@ -9,7 +9,8 @@ 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. +({class}`~libvcs.cmd.git.GitStashManager`) for the new +{ref}`Manager/Cmd pattern `. ::: ### Examples diff --git a/docs/cmd/git/submodule.md b/docs/cmd/git/submodule.md index 58eedbf7d..32b798602 100644 --- a/docs/cmd/git/submodule.md +++ b/docs/cmd/git/submodule.md @@ -1,6 +1,6 @@ # `submodule` -For `git-submodule(1)`. +For [`git-submodule(1)`](https://git-scm.com/docs/git-submodule). ## Overview @@ -9,7 +9,8 @@ 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. +({class}`~libvcs.cmd.git.GitSubmoduleManager`) for the new +{ref}`Manager/Cmd pattern `. ::: ### Examples diff --git a/docs/cmd/git/tag.md b/docs/cmd/git/tag.md index b4f810906..09f5ce3c0 100644 --- a/docs/cmd/git/tag.md +++ b/docs/cmd/git/tag.md @@ -1,6 +1,6 @@ # `tag` -For `git-tag(1)`. +For [`git-tag(1)`](https://git-scm.com/docs/git-tag). ## Overview diff --git a/docs/cmd/git/worktree.md b/docs/cmd/git/worktree.md index 732f7eacb..2f87c5243 100644 --- a/docs/cmd/git/worktree.md +++ b/docs/cmd/git/worktree.md @@ -1,6 +1,6 @@ # `worktree` -For `git-worktree(1)`. +For [`git-worktree(1)`](https://git-scm.com/docs/git-worktree). ## Overview diff --git a/docs/cmd/index.md b/docs/cmd/index.md index 686e868b0..04cc9c324 100644 --- a/docs/cmd/index.md +++ b/docs/cmd/index.md @@ -15,9 +15,9 @@ versions. ## Overview -The `libvcs.cmd` module provides Python wrappers for VCS command-line tools: +The {mod}`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.git` - Git commands with {ref}`Managers ` for intuitive entity traversal and Commands for targeted execution - {mod}`libvcs.cmd.hg` - Mercurial commands - {mod}`libvcs.cmd.svn` - Subversion commands @@ -26,7 +26,7 @@ The `libvcs.cmd` module provides Python wrappers for VCS command-line tools: | Module | Use Case | |--------|----------| | `libvcs.cmd` | Fine-grained control over individual VCS commands | -| `libvcs.sync` | High-level repository cloning and updating | +| {mod}`libvcs.sync` | High-level repository cloning and updating | ## Modules diff --git a/docs/index.md b/docs/index.md index 85589c3c9..ed48ad1ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -61,7 +61,7 @@ $ uv add libvcs libvcs is pre-1.0. Pin to a range: `libvcs>=0.39,<0.40` ``` -See [Quickstart](quickstart.md) for all methods and first steps. +See {ref}`quickstart` for all methods and first steps. ## At a glance @@ -88,7 +88,7 @@ updates a local checkout in one call. ## Testing -libvcs ships a [pytest plugin](/api/pytest-plugin/) with +libvcs ships a {doc}`pytest plugin ` with session-scoped fixtures for Git, SVN, and Mercurial repositories: ```python diff --git a/docs/internals/query_list.md b/docs/internals/query_list.md index 961ef4421..a832cb624 100644 --- a/docs/internals/query_list.md +++ b/docs/internals/query_list.md @@ -1,6 +1,7 @@ # List querying - `libvcs._internal.query_list` -`QueryList` is the backbone of the Manager/Cmd pattern. Every `ls()` method in +{class}`~libvcs._internal.query_list.QueryList` is the backbone of the +{ref}`Manager/Cmd pattern `. Every `ls()` method in libvcs returns a `QueryList`, enabling chainable filtering on the results. ## How It's Used @@ -23,7 +24,9 @@ True ## Filtering -`QueryList` extends Python's built-in `list` with Django-style lookups. +`QueryList` extends Python's built-in {class}`list` with +[Django-style](https://docs.djangoproject.com/en/stable/topics/db/queries/) +lookups. Filter on any attribute of the contained objects — exact by default, or with a lookup suffix: diff --git a/docs/project/contributing.md b/docs/project/contributing.md index b88487b9d..e7408ac87 100644 --- a/docs/project/contributing.md +++ b/docs/project/contributing.md @@ -6,5 +6,5 @@ As an open source project, libvcs accepts contributions through GitHub. -Ready to dive in? See the [Development Workflow](workflow.md) for +Ready to dive in? See the {ref}`Development Workflow ` for environment setup, running tests, linting, and building docs. diff --git a/docs/quickstart.md b/docs/quickstart.md index 73c2488da..3c0ec848f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -84,7 +84,8 @@ Clone an existing repository and check its status: ### Subcommand Managers -Work with branches, tags, remotes, and more using the Manager/Cmd pattern: +Work with branches, tags, remotes, and more using the +{ref}`Manager/Cmd pattern `: ```python >>> from libvcs.cmd.git import Git diff --git a/docs/sync/base.md b/docs/sync/base.md index d74ca5fa3..89f83d418 100644 --- a/docs/sync/base.md +++ b/docs/sync/base.md @@ -2,7 +2,8 @@ Base objects / classes for projects. -Adding your own VCS / Extending libvcs can be done through subclassing `BaseSync`. +Adding your own VCS / Extending libvcs can be done through subclassing +{class}`~libvcs.sync.base.BaseSync`. ```{eval-rst} .. automodule:: libvcs.sync.base diff --git a/docs/topics/filtering.md b/docs/topics/filtering.md index dadac9947..ce105f39f 100644 --- a/docs/topics/filtering.md +++ b/docs/topics/filtering.md @@ -2,13 +2,16 @@ # QueryList Filtering -libvcs uses `QueryList` to enable Django-style filtering on git entities. +libvcs uses {class}`~libvcs._internal.query_list.QueryList` to enable +[Django-style](https://docs.djangoproject.com/en/stable/topics/db/queries/) +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: +The {meth}`~libvcs._internal.query_list.QueryList.filter` method accepts +keyword arguments with optional lookup suffixes: ```python >>> from libvcs.cmd.git import Git @@ -75,7 +78,8 @@ True ## Getting a Single Item -Use `get()` to retrieve exactly one matching item: +Use {meth}`~libvcs._internal.query_list.QueryList.get` to retrieve exactly +one matching item: ```python >>> from libvcs.cmd.git import Git @@ -85,7 +89,9 @@ Use `get()` to retrieve exactly one matching item: 'master' ``` -If no match or multiple matches are found, `get()` raises an exception. +If no match is found, `get()` raises +{exc}`~libvcs._internal.query_list.ObjectDoesNotExist`; multiple matches +raise {exc}`~libvcs._internal.query_list.MultipleObjectsReturned`. ## Chaining Filters diff --git a/docs/topics/traversing_git.md b/docs/topics/traversing_git.md index 66ec80494..c5be20677 100644 --- a/docs/topics/traversing_git.md +++ b/docs/topics/traversing_git.md @@ -245,7 +245,8 @@ True ## Error Handling -When `get()` finds no match, it raises `ObjectDoesNotExist`: +When `get()` finds no match, it raises +{exc}`~libvcs._internal.query_list.ObjectDoesNotExist`: ```python >>> from libvcs.cmd.git import Git diff --git a/docs/topics/url_parsing.md b/docs/topics/url_parsing.md index 7dc459321..1502a4ad0 100644 --- a/docs/topics/url_parsing.md +++ b/docs/topics/url_parsing.md @@ -3,12 +3,13 @@ # 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. +Think of it as {mod}`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: +Use {meth}`GitURL.is_valid() ` to check if a +string is a valid VCS URL: ```python >>> from libvcs.url.git import GitURL @@ -22,7 +23,7 @@ False ## Parsing URLs -Create a URL object to extract components: +Create a {class}`~libvcs.url.git.GitURL` to extract components: ```python >>> from libvcs.url.git import GitURL @@ -63,7 +64,8 @@ Git's SCP-style syntax (`user@host:path`) is also supported: ## Converting URL Formats -Use `to_url()` to export a URL in a specific format: +Use {meth}`~libvcs.url.git.GitURL.to_url` to export a URL in a specific +format: ```python >>> from libvcs.url.git import GitURL @@ -74,7 +76,8 @@ Use `to_url()` to export a URL in a specific format: ## Pip-style URLs -libvcs handles pip-style VCS URLs with branch/tag specifiers: +libvcs handles [pip-style VCS URLs](https://pip.pypa.io/en/stable/topics/vcs-support/) +with branch/tag specifiers: ```python >>> from libvcs.url.git import GitURL @@ -89,6 +92,9 @@ libvcs handles pip-style VCS URLs with branch/tag specifiers: ### Mercurial +The same interface works for Mercurial through +{class}`~libvcs.url.hg.HgURL`: + ```python >>> from libvcs.url.hg import HgURL >>> HgURL.is_valid(url='https://hg.mozilla.org/mozilla-central') @@ -100,6 +106,8 @@ True ### Subversion +Subversion URLs parse through {class}`~libvcs.url.svn.SvnURL`: + ```python >>> from libvcs.url.svn import SvnURL >>> SvnURL.is_valid(url='svn+ssh://svn.example.org/repo/trunk') @@ -111,7 +119,7 @@ True ## URL Registry -The registry can auto-detect VCS type from a URL: +The {doc}`URL registry ` can auto-detect VCS type from a URL: ```python >>> from libvcs.url.registry import registry diff --git a/docs/url/git.md b/docs/url/git.md index 64663376e..0e5db4edf 100644 --- a/docs/url/git.md +++ b/docs/url/git.md @@ -8,7 +8,8 @@ myst: # 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 +Detect, parse, and change git URLs using libvcs's URL parser for +[`git(1)`](https://git-scm.com/docs/git). It builds on top of the {ref}`VCS-friendly URL parser framework `. ```{eval-rst} From 30abf4a84df37c8e63268cf2fa3d519d907b8c16 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 1 Jul 2026 23:01:01 -0500 Subject: [PATCH 03/10] docs(voice): Repair registry fence, url hub, and stub pages why: url/registry.md trapped its explanatory prose inside one fenced code block (rendered as monospace, markup unprocessed); url/index.md opened with first-person musing before the concept; hub and stub pages led with comparison links or bare fragments instead of saying what the module does. what: - url/registry.md: split the monolithic fence into three self-contained doctest stories with real prose between them, concept-first intro, real headings, linked Rule/RuleMap/ VCSRegistry/ParserMatch, and an API Reference heading - url/index.md: concept-first intro; prose leads for the tab sections; explain is_explicit; fix "how to we get" and truncated "For orgs on ," sentences; correct the cb: shorthand example and the failing git-clone transcript; drop the comment inside the console block; {mod}`urlparse` -> {mod}`urllib.parse`; merge the Matchers stub sections into one "How matching resolves" section - cmd/index.md, sync/index.md: one-sentence concept line above the Compare-to links; sync hub names and links GitSync/HgSync/SvnSync - Stub one-liners (cmd/{hg,svn}, sync/{base,git,hg,svn}, url/{hg,svn}): concept sentence linking the class, the external project, and the man page; capitalize proper nouns --- docs/cmd/hg.md | 4 +- docs/cmd/index.md | 3 ++ docs/cmd/svn.md | 3 +- docs/sync/base.md | 6 +-- docs/sync/git.md | 4 +- docs/sync/hg.md | 4 +- docs/sync/index.md | 5 ++ docs/sync/svn.md | 3 +- docs/url/hg.md | 4 +- docs/url/index.md | 87 +++++++++++++++------------------ docs/url/registry.md | 114 +++++++++++++++++++++++++++---------------- docs/url/svn.md | 3 +- 12 files changed, 137 insertions(+), 103 deletions(-) diff --git a/docs/cmd/hg.md b/docs/cmd/hg.md index 73c1c44c5..61939b56d 100644 --- a/docs/cmd/hg.md +++ b/docs/cmd/hg.md @@ -1,6 +1,8 @@ # `libvcs.cmd.hg` -For mercurial, aka `hg(1)`. +Run [Mercurial](https://www.mercurial-scm.org/) commands through +{class}`~libvcs.cmd.hg.Hg`, aka +[`hg(1)`](https://www.mercurial-scm.org/doc/hg.1.html). ```{eval-rst} .. automodule:: libvcs.cmd.hg diff --git a/docs/cmd/index.md b/docs/cmd/index.md index 04cc9c324..ff1e0416c 100644 --- a/docs/cmd/index.md +++ b/docs/cmd/index.md @@ -2,6 +2,9 @@ # Commands - `libvcs.cmd` +Run git, hg, and svn from Python through typed wrappers — one class per VCS +binary, one method per operation. + 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) diff --git a/docs/cmd/svn.md b/docs/cmd/svn.md index d3f39dcfe..ee6d2ae9c 100644 --- a/docs/cmd/svn.md +++ b/docs/cmd/svn.md @@ -1,6 +1,7 @@ # `libvcs.cmd.svn` -For subversion, aka `svn(1)` +Run [Subversion](https://subversion.apache.org/) commands through +{class}`~libvcs.cmd.svn.Svn`, aka `svn(1)`. ```{eval-rst} .. automodule:: libvcs.cmd.svn diff --git a/docs/sync/base.md b/docs/sync/base.md index 89f83d418..404234b23 100644 --- a/docs/sync/base.md +++ b/docs/sync/base.md @@ -1,9 +1,7 @@ # `libvcs.sync.base` -Base objects / classes for projects. - -Adding your own VCS / Extending libvcs can be done through subclassing -{class}`~libvcs.sync.base.BaseSync`. +Foundation for the sync classes. Add your own VCS — or extend libvcs — by +subclassing {class}`~libvcs.sync.base.BaseSync`. ```{eval-rst} .. automodule:: libvcs.sync.base diff --git a/docs/sync/git.md b/docs/sync/git.md index 6faa4aefb..8b3645b1e 100644 --- a/docs/sync/git.md +++ b/docs/sync/git.md @@ -1,6 +1,8 @@ # `libvcs.sync.git` -For `git(1)`. +Clone and update git repositories through +{class}`~libvcs.sync.git.GitSync`, for +[`git(1)`](https://git-scm.com/docs/git). Compare to: [`fabtools.require.git`](https://fabtools.readthedocs.io/en/0.19.0/api/require/git.html), diff --git a/docs/sync/hg.md b/docs/sync/hg.md index b7f441742..7a40f4d22 100644 --- a/docs/sync/hg.md +++ b/docs/sync/hg.md @@ -1,6 +1,8 @@ # `libvcs.sync.hg` -For mercurial, aka `hg(1)`. +Clone and update [Mercurial](https://www.mercurial-scm.org/) repositories +through {class}`~libvcs.sync.hg.HgSync`, aka +[`hg(1)`](https://www.mercurial-scm.org/doc/hg.1.html). ```{eval-rst} .. automodule:: libvcs.sync.hg diff --git a/docs/sync/index.md b/docs/sync/index.md index 46f53c84e..996b1feb7 100644 --- a/docs/sync/index.md +++ b/docs/sync/index.md @@ -2,6 +2,11 @@ # Sync - `libvcs.sync` +Keep a local checkout in sync with its remote: one call clones the +repository when it doesn't exist yet and updates it when it does, through +{class}`~libvcs.sync.git.GitSync`, {class}`~libvcs.sync.hg.HgSync`, and +{class}`~libvcs.sync.svn.SvnSync` — built on top of {mod}`libvcs.cmd`. + 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), diff --git a/docs/sync/svn.md b/docs/sync/svn.md index 43cfe2871..8e401312c 100644 --- a/docs/sync/svn.md +++ b/docs/sync/svn.md @@ -1,6 +1,7 @@ # `libvcs.sync.svn` -For subversion, aka `svn(1)` +Check out and update [Subversion](https://subversion.apache.org/) working +copies through {class}`~libvcs.sync.svn.SvnSync`, aka `svn(1)`. ```{eval-rst} .. automodule:: libvcs.sync.svn diff --git a/docs/url/hg.md b/docs/url/hg.md index 8efbf3e66..444dcf026 100644 --- a/docs/url/hg.md +++ b/docs/url/hg.md @@ -1,6 +1,8 @@ # Mercurial URL Parser - `libvcs.url.hg` -For hg, aka `hg(1)`. +Detect and parse [Mercurial](https://www.mercurial-scm.org/) URLs through +{class}`~libvcs.url.hg.HgURL`, aka +[`hg(1)`](https://www.mercurial-scm.org/doc/hg.1.html). ```{eval-rst} .. automodule:: libvcs.url.hg diff --git a/docs/url/index.md b/docs/url/index.md index d02e72f5c..0ef8722a3 100644 --- a/docs/url/index.md +++ b/docs/url/index.md @@ -2,17 +2,14 @@ # URL Parser - `libvcs.url` -We all love {mod}`urllib.parse`, but what about VCS systems? +Parse VCS URLs into typed, editable structures — {mod}`urllib.parse` for git, +Mercurial, and Subversion. You validate a URL string, read its parts back as +{mod}`dataclasses` fields (`hostname`, `path`, `rev`), change any of them, +and export a form the VCS binary accepts. -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. +The common URL shapes work out of the box; when your host or shorthand +isn't covered, you can [add your own rules](#extendability). For a guided +tour, start with {ref}`url-parsing`. ## Modules @@ -59,6 +56,8 @@ Shared regex patterns and URL constants. ## Validate and detect VCS URLs +Check whether a string is a URL the VCS recognizes: + ````{tab} git {meth}`libvcs.url.git.GitURL.is_valid()` @@ -122,7 +121,8 @@ True ## Parse VCS URLs -_Compare to {class}`urllib.parse.ParseResult`_ +Turn a URL string into a typed structure with named fields — compare to +{class}`urllib.parse.ParseResult`: ````{tab} git @@ -185,24 +185,21 @@ SvnURL(url=svn+ssh://svn.debian.org/svn/aliothproj/path/in/project/repository, - 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`. +`pip` knows what a certain URL string means, but `git clone` won't. This +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: +but `git clone` can't use that URL: ```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'. +$ git clone git+https://github.com/django/django.git@3.2 +Cloning into 'django.git@3.2'... +fatal: Unable to find remote helper for 'git+https' ``` It needs something like this: @@ -211,23 +208,18 @@ It needs something like this: $ 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 +That translation is why parsing returns a structure — `GitURL` — rather +than a string. As with {class}`urllib.parse.ParseResult`, you inspect or +replace individual fields (swap just the hostname, drop the scheme), then +call `to_url()` when you finally need a string the VCS accepts. The same +structure covers the popular hosts out of the box and takes custom rules +for everything else. ## Scope ### Out of the box -The ambition for this is to build extendable parsers for package-like URLs, e.g. +libvcs parses package-like URLs, e.g. - Vanilla VCS URLs @@ -255,12 +247,12 @@ The ambition for this is to build extendable parsers for package-like URLs, e.g. ## Extendability Patterns can be registered. [Similar behavior](https://stackoverflow.com/a/6264214/1396928) exists -in {mod}`urlparse` (undocumented). +in {mod}`urllib.parse` (undocumented). -- Any formats not covered by the stock -- Custom urls +- Any formats not covered by the stock rules +- Custom URLs - - For orgs on , e.g: + - For orgs on GitHub or GitLab, e.g.: - `python:mypy` -> `git@github.com:python/mypy.git` - `inkscape:inkscape` -> `git@gitlab.com:inkscape/inkscape.git` @@ -269,7 +261,7 @@ in {mod}`urlparse` (undocumented). Direct to site: - - `cb:python-vcs/libtmux` -> `https://codeberg.org/vcs-python/libvcs` + - `cb:vcs-python/libvcs` -> `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`] @@ -284,20 +276,17 @@ in {mod}`urlparse` (undocumented). [`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. +From there, `GitURL` can be used downstream directly by other projects: +libvcs's own {ref}`cmd` and {ref}`sync ` layers, as well as +[vcspull configurations](https://vcspull.git-pull.com/), detect and accept +these URL patterns. -### Matchers: First wins +### How matching resolves -When registering new matchers, higher `weight`s are checked first. If it's a valid regex grouping, -it will be picked. +When a rule matches, its `defaults` fill in the groups the pattern didn't +capture — a `github:` prefix implies `hostname=github.com`. When several +rules could match, higher `weight`s are checked first; the first rule whose +pattern produces a valid match wins. ```{toctree} :hidden: diff --git a/docs/url/registry.md b/docs/url/registry.md index 21d9e4edb..7afd93d63 100644 --- a/docs/url/registry.md +++ b/docs/url/registry.md @@ -1,8 +1,18 @@ # VCS Detection - `libvcs.url.registry` -Detect VCS from `git`, `hg`, and `svn` URLs. +Detect which VCS a URL belongs to — git, Mercurial, or Subversion — before +you shell out to any binary. The module-level registry checks a URL against +every parser — {class}`~libvcs.url.git.GitURL`, +{class}`~libvcs.url.hg.HgURL`, {class}`~libvcs.url.svn.SvnURL` — and returns +each hit as a {class}`~libvcs.url.registry.ParserMatch`. Most readers only +need {meth}`~libvcs.url.registry.VCSRegistry.match`; registering rules of +your own is the rarer case covered further down. -**Basic example:** +## Matching URLs + +Pass `is_explicit` to narrow matching to rules where the URL names its VCS +outright (`True`) — like the `git+ssh://` pip-style scheme — or to +pattern-inference rules only (`False`): ```python >>> from libvcs.url.registry import registry, ParserMatch @@ -24,19 +34,29 @@ Detect VCS from `git`, `hg`, and `svn` URLs. [ParserMatch(vcs='git', match=GitURL(...))] ``` -**From the ground up:** +## Adding your own rules + +For the rarer cases — organization shorthands, self-hosted forges — teach a +parser new URL shapes: subclass {class}`~libvcs.url.base.Rule` for the +pattern, attach it to your own {class}`~libvcs.url.git.GitURL` subclass +through {class}`~libvcs.url.base.RuleMap`, and hand that parser to a fresh +{class}`~libvcs.url.registry.VCSRegistry`. Subclassing keeps the rules +local — registering on `GitURL.rule_map` directly would mutate the shared +class-level map and change `GitURL` for every caller in the process. + +This registry understands `github:org/repo` and converts matches to +cloneable URLs. An ambiguous SSH URL still matches every VCS — narrow it +with `is_explicit=True`: ```python >>> import dataclasses >>> from libvcs.url.base import Rule, RuleMap ->>> from libvcs.url.registry import ParserMatch, VCSRegistry, registry +>>> 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' +... description = 'Matches prefixes like github:org/repo' ... pattern = r'^github:(?P.*)$' ... defaults = { ... 'hostname': 'github.com', @@ -45,33 +65,15 @@ This will match `github:org/repo`: ... 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, ... } ... ) -Subclassing with its own ``RuleMap`` keeps these rules local. Registering on -``GitURL.rule_map`` instead would mutate the shared class-level map and change -``GitURL`` for every caller in the process. - >>> my_parsers: "ParserLazyMap" = { ... "git": MyGitURLParser, ... "hg": "libvcs.url.hg.HgURL", @@ -80,19 +82,11 @@ Subclassing with its own ``RuleMap`` keeps these rules local. Registering on >>> vcs_matcher = VCSRegistry(parsers=my_parsers) -Each registry owns its parsers, so building a custom one leaves the -module-level ``registry`` untouched -- it still resolves git to ``GitURL``: - ->>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git') -[ParserMatch(vcs='git', match=GitURL(...))] - >>> 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(...))] @@ -109,16 +103,52 @@ Still works with everything GitURL does: >>> 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' +``` + +The same pattern handles infrastructure shorthands like KDE's +`kde:group/repository` convention — and a custom registry never leaks into +the module-level one, which still resolves the same URLs to plain +{class}`~libvcs.url.git.GitURL`: + +```python +>>> import dataclasses +>>> from libvcs.url.base import Rule, RuleMap +>>> from libvcs.url.registry import ParserMatch, VCSRegistry, registry +>>> from libvcs.url.git import GitURL + +>>> 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 MyKDEURLParser(GitURL): +... rule_map = RuleMap( +... _rule_map={ +... **GitURL.rule_map._rule_map, +... 'kde_prefix': KDEPrefix, +... } +... ) + +>>> vcs_matcher = VCSRegistry(parsers={ +... "git": MyKDEURLParser, +... "hg": "libvcs.url.hg.HgURL", +... "svn": "libvcs.url.svn.SvnURL", +... }) >>> vcs_matcher.match('kde:frameworks/kirigami', is_explicit=True) [ParserMatch(vcs='git', - match=MyGitURLParser(url=kde:frameworks/kirigami, + match=MyKDEURLParser(url=kde:frameworks/kirigami, scheme=https, hostname=invent.kde.org, path=frameworks/kirigami, @@ -126,13 +156,6 @@ If an ssh URL is preferred: >>> 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' @@ -140,8 +163,13 @@ MyGitURLParser(url=kde:frameworks/kirigami, >>> kde_match.to_url() 'git@invent.kde.org:frameworks/kirigami' + +>>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git') +[ParserMatch(vcs='git', match=GitURL(...))] ``` +## API Reference + ```{eval-rst} .. automodule:: libvcs.url.registry :members: diff --git a/docs/url/svn.md b/docs/url/svn.md index 190089a8e..494558fd8 100644 --- a/docs/url/svn.md +++ b/docs/url/svn.md @@ -1,6 +1,7 @@ # SVN URL Parser - `libvcs.url.svn` -For svn, aka `svn(1)`. +Detect and parse [Subversion](https://subversion.apache.org/) URLs through +{class}`~libvcs.url.svn.SvnURL`, aka `svn(1)`. ```{eval-rst} .. automodule:: libvcs.url.svn From 3227ca89aa6fc8d6613d1d5bef777490dd0d2a02 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 17:26:40 -0500 Subject: [PATCH 04/10] docs(project): Fix workflow commands and stale references why: workflow.md documented a uv command that does not exist and a Releasing flow that contradicts project/releasing.md and the current CHANGES format; pin examples cited a version range two releases old; quickstart never delivered the URL parsing its index card promises. what: - workflow.md: `uv install -E ...` -> `uv sync`; `uv run ruff` -> `uv run ruff check .`; "From home directory" -> project root; split run-on helper sentences; replace the stale Releasing section (RST-era changelog format, old paths, multi-command literal block) with a pointer to {ref}`releasing` - index.md, api/index.md: refresh pin example to the current range - quickstart.md: add a Parse URLs section with a runnable GitURL example linking {ref}`url-parsing`; fix developmental-release wording --- docs/api/index.md | 2 +- docs/index.md | 2 +- docs/project/workflow.md | 62 ++++++---------------------------------- docs/quickstart.md | 19 +++++++++++- 4 files changed, 29 insertions(+), 56 deletions(-) diff --git a/docs/api/index.md b/docs/api/index.md index 61e6ab382..001eb1cda 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -8,7 +8,7 @@ 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`. +Pin to a range, e.g. `libvcs>=0.45,<0.46`. ## Subsystems diff --git a/docs/index.md b/docs/index.md index ed48ad1ee..4c2b68798 100644 --- a/docs/index.md +++ b/docs/index.md @@ -58,7 +58,7 @@ $ uv add libvcs ``` ```{tip} -libvcs is pre-1.0. Pin to a range: `libvcs>=0.39,<0.40` +libvcs is pre-1.0. Pin to a range, e.g. `libvcs>=0.45,<0.46` ``` See {ref}`quickstart` for all methods and first steps. diff --git a/docs/project/workflow.md b/docs/project/workflow.md index d565dc113..e81e99d35 100644 --- a/docs/project/workflow.md +++ b/docs/project/workflow.md @@ -4,7 +4,7 @@ ## Development environment -[uv] is a required package to develop. +Development requires [uv]. ```console $ git clone https://github.com/vcs-python/libvcs.git @@ -14,8 +14,10 @@ $ git clone https://github.com/vcs-python/libvcs.git $ cd libvcs ``` +Install the project and its development dependency groups: + ```console -$ uv install -E "docs test coverage lint" +$ uv sync ``` Justfile commands prefixed with `watch-` will watch files and rerun. @@ -26,7 +28,7 @@ Justfile commands prefixed with `watch-` will watch files and rerun. $ uv run py.test ``` -Helpers: `just test` Rerun tests on file change: `just watch-test` (requires [entr(1)]) +Helper: `just test`. Rerun tests on file change: `just watch-test` (requires [entr(1)]). ### Running tests in parallel @@ -59,7 +61,7 @@ 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` +From the project root: `just start-docs`. From inside `docs/`: `just start`. [sphinx-autobuild]: https://github.com/executablebooks/sphinx-autobuild @@ -84,7 +86,7 @@ The project uses [ruff] to handle formatting, sorting imports and linting. uv: ```console -$ uv run ruff +$ uv run ruff check . ``` If you setup manually: @@ -196,55 +198,9 @@ 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 +See {ref}`releasing` for the version policy and release checklist. -[uv]: https://github.com/astral-sh/uv +[uv]: https://github.com/astral-sh/uv [pytest-xdist]: https://pytest-xdist.readthedocs.io/ [entr(1)]: http://eradman.com/entrproject/ [`entr(1)`]: http://eradman.com/entrproject/ diff --git a/docs/quickstart.md b/docs/quickstart.md index 3c0ec848f..a2edf476c 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -21,7 +21,7 @@ $ pip install --user --upgrade libvcs ### 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. +Their version numbers carry suffixes like `a1`, `b1`, and `rc1`, respectively. `1.10.0b4` would mean the 4th beta release of `1.10.0` before general availability. - [pip]\: @@ -55,6 +55,23 @@ via trunk (can break easily): ## Basic Usage +### Parse URLs + +Detect and parse a VCS URL with {class}`~libvcs.url.git.GitURL`: + +```python +>>> from libvcs.url.git import GitURL +>>> GitURL.is_valid(url='git@github.com:vcs-python/libvcs.git') +True +>>> url = GitURL(url='git@github.com:vcs-python/libvcs.git') +>>> url.hostname +'github.com' +>>> url.to_url() +'git@github.com:vcs-python/libvcs.git' +``` + +See {ref}`url-parsing` for the full tour. + ### Commands Run git commands directly using {class}`~libvcs.cmd.git.Git`. Initialize a From f7b8bf32b60af333075b011ef18b9bdafe5bcae5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 17:26:54 -0500 Subject: [PATCH 05/10] docs(CHANGES): note runnable documentation examples why: Users of published releases copied documented calls that raise TypeError or return nothing; the changelog should record that every docs example now executes and that the drifted calls were corrected. what: - Add Documentation deliverable covering the doctest conversion and the API drift it surfaced --- CHANGES | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 4caec3510..6cdf6ce99 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,20 @@ $ uv add libvcs --prerelease allow _Notes on the upcoming release will go here._ +### Documentation + +#### Documentation examples now run as doctests + +Every Python example under `docs/` executes against real temporary +repositories on each test run, so copied examples work as printed. The +conversion surfaced and fixed documented calls that had drifted from the +API: `git.branches.create()` and `remote.set_url()` shown positionally +where keyword arguments are required, `notes.add(object=...)` for what is +named `object_sha`, `reflog.ls(ref=...)` and `expire(ref=...)` parameters +that don't exist, `worktrees.add(branch=...)` for `new_branch`, and +{class}`~libvcs._internal.query_list.QueryList` filter examples using +attribute names the objects don't have. + ## libvcs 0.45.0 (2026-06-28) libvcs 0.45.0 makes test behavior independent of execution order by removing shared state from the URL registry and the pytest fixtures. {class}`~libvcs.url.registry.VCSRegistry` now keeps its parsers per instance, so building a custom registry no longer mutates the module-level `registry`, and the `git_repo`, `hg_repo`, and `svn_repo` fixtures hand every test its own clone instead of a shared checkout — fixing cross-test state leakage for downstream test suites such as vcspull. Contributors also gain an opt-in parallel test mode via pytest-xdist. From 21b298adc039ce2d6f92c42a9c307d3549e0385b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 17:26:54 -0500 Subject: [PATCH 06/10] docs(CHANGES): note first-mention cross-references why: Readers navigating the docs gain links from prose to API pages; the changelog should record the cross-reference and page-voice pass. what: - Add Documentation deliverable covering first-mention linking, man-page links, the registry guide restructure, and hub openings --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index 6cdf6ce99..da6cbd424 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,14 @@ that don't exist, `worktrees.add(branch=...)` for `new_branch`, and {class}`~libvcs._internal.query_list.QueryList` filter examples using attribute names the objects don't have. +#### Cross-references link objects on first mention + +Documentation pages now link classes, methods, and exceptions to their API +reference the first time prose names them, and man-page citations link to +upstream documentation. The {mod}`libvcs.url.registry` guide untangles its +examples into narrated, runnable steps, and the module hub pages open with +what each layer does. + ## libvcs 0.45.0 (2026-06-28) libvcs 0.45.0 makes test behavior independent of execution order by removing shared state from the URL registry and the pytest fixtures. {class}`~libvcs.url.registry.VCSRegistry` now keeps its parsers per instance, so building a custom registry no longer mutates the module-level `registry`, and the `git_repo`, `hg_repo`, and `svn_repo` fixtures hand every test its own clone instead of a shared checkout — fixing cross-test state leakage for downstream test suites such as vcspull. Contributors also gain an opt-in parallel test mode via pytest-xdist. From 60b74c69c3b4f76d23dd08eb26d44b18b003b87a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 17:41:09 -0500 Subject: [PATCH 07/10] docs(CHANGES): Add PR refs to deliverables why: The changelog convention places the pull request number in each deliverable heading; the number was unknown until the branch went up for review. what: - Add (#540) to both Documentation deliverable headings --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index da6cbd424..18e5c31b9 100644 --- a/CHANGES +++ b/CHANGES @@ -22,7 +22,7 @@ _Notes on the upcoming release will go here._ ### Documentation -#### Documentation examples now run as doctests +#### Documentation examples now run as doctests (#540) Every Python example under `docs/` executes against real temporary repositories on each test run, so copied examples work as printed. The @@ -34,7 +34,7 @@ that don't exist, `worktrees.add(branch=...)` for `new_branch`, and {class}`~libvcs._internal.query_list.QueryList` filter examples using attribute names the objects don't have. -#### Cross-references link objects on first mention +#### Cross-references link objects on first mention (#540) Documentation pages now link classes, methods, and exceptions to their API reference the first time prose names them, and man-page citations link to From 5d0a25b531c19fc5b06d8087204c7c3b1f16cc07 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 18:23:55 -0500 Subject: [PATCH 08/10] docs(svn): Link svn(1) man-page citations why: The branch links every git and hg man-page citation to upstream documentation and CHANGES claims as much, but the three svn pages left svn(1) as plain code text. what: - Link svn(1) to the svn command reference in the SVN book on cmd/svn.md, sync/svn.md, and url/svn.md --- docs/cmd/svn.md | 3 ++- docs/sync/svn.md | 3 ++- docs/url/svn.md | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/cmd/svn.md b/docs/cmd/svn.md index ee6d2ae9c..e6a29c376 100644 --- a/docs/cmd/svn.md +++ b/docs/cmd/svn.md @@ -1,7 +1,8 @@ # `libvcs.cmd.svn` Run [Subversion](https://subversion.apache.org/) commands through -{class}`~libvcs.cmd.svn.Svn`, aka `svn(1)`. +{class}`~libvcs.cmd.svn.Svn`, aka +[`svn(1)`](https://svnbook.red-bean.com/en/1.7/svn.ref.svn.html). ```{eval-rst} .. automodule:: libvcs.cmd.svn diff --git a/docs/sync/svn.md b/docs/sync/svn.md index 8e401312c..efddad988 100644 --- a/docs/sync/svn.md +++ b/docs/sync/svn.md @@ -1,7 +1,8 @@ # `libvcs.sync.svn` Check out and update [Subversion](https://subversion.apache.org/) working -copies through {class}`~libvcs.sync.svn.SvnSync`, aka `svn(1)`. +copies through {class}`~libvcs.sync.svn.SvnSync`, aka +[`svn(1)`](https://svnbook.red-bean.com/en/1.7/svn.ref.svn.html). ```{eval-rst} .. automodule:: libvcs.sync.svn diff --git a/docs/url/svn.md b/docs/url/svn.md index 494558fd8..d2da02458 100644 --- a/docs/url/svn.md +++ b/docs/url/svn.md @@ -1,7 +1,8 @@ # SVN URL Parser - `libvcs.url.svn` Detect and parse [Subversion](https://subversion.apache.org/) URLs through -{class}`~libvcs.url.svn.SvnURL`, aka `svn(1)`. +{class}`~libvcs.url.svn.SvnURL`, aka +[`svn(1)`](https://svnbook.red-bean.com/en/1.7/svn.ref.svn.html). ```{eval-rst} .. automodule:: libvcs.url.svn From a1900a01991bdb16e38e6b5f27794ed9c9b5cf33 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 18:25:20 -0500 Subject: [PATCH 09/10] url/registry(docs): Keep independence proof by primary example why: de3011e added a doctest proving a custom VCSRegistry leaves the module-level registry untouched, placed beside the custom registry's construction; the page restructure had moved it to the end of the second story, away from the example it guards. what: - Show registry.match() resolving to plain GitURL immediately after vcs_matcher is built in the first custom-rule story, contrasting with MyGitURLParser on the next call - Drop the duplicate proof and its lead-in from the KDE story --- docs/url/registry.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/url/registry.md b/docs/url/registry.md index 7afd93d63..e46e33671 100644 --- a/docs/url/registry.md +++ b/docs/url/registry.md @@ -45,13 +45,13 @@ local — registering on `GitURL.rule_map` directly would mutate the shared class-level map and change `GitURL` for every caller in the process. This registry understands `github:org/repo` and converts matches to -cloneable URLs. An ambiguous SSH URL still matches every VCS — narrow it -with `is_explicit=True`: +cloneable URLs, leaving the module-level `registry` untouched. An ambiguous +SSH URL still matches every VCS — narrow it with `is_explicit=True`: ```python >>> import dataclasses >>> from libvcs.url.base import Rule, RuleMap ->>> from libvcs.url.registry import ParserMatch, VCSRegistry +>>> from libvcs.url.registry import ParserMatch, VCSRegistry, registry >>> from libvcs.url.git import GitURL >>> class GitHubPrefix(Rule): @@ -82,6 +82,9 @@ with `is_explicit=True`: >>> vcs_matcher = VCSRegistry(parsers=my_parsers) +>>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git') +[ParserMatch(vcs='git', match=GitURL(...))] + >>> vcs_matcher.match('git@invent.kde.org:plasma/plasma-sdk.git') [ParserMatch(vcs='git', match=MyGitURLParser(...)), ParserMatch(vcs='hg', match=HgURL(...)), @@ -110,14 +113,12 @@ with `is_explicit=True`: ``` The same pattern handles infrastructure shorthands like KDE's -`kde:group/repository` convention — and a custom registry never leaks into -the module-level one, which still resolves the same URLs to plain -{class}`~libvcs.url.git.GitURL`: +`kde:group/repository` convention: ```python >>> import dataclasses >>> from libvcs.url.base import Rule, RuleMap ->>> from libvcs.url.registry import ParserMatch, VCSRegistry, registry +>>> from libvcs.url.registry import ParserMatch, VCSRegistry >>> from libvcs.url.git import GitURL >>> class KDEPrefix(Rule): # https://community.kde.org/Infrastructure/Git @@ -163,9 +164,6 @@ the module-level one, which still resolves the same URLs to plain >>> kde_match.to_url() 'git@invent.kde.org:frameworks/kirigami' - ->>> registry.match('git@invent.kde.org:plasma/plasma-sdk.git') -[ParserMatch(vcs='git', match=GitURL(...))] ``` ## API Reference From 663c940e937d0592654e6453fb70aac80b8869de Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jul 2026 19:03:19 -0500 Subject: [PATCH 10/10] docs(CHANGES): Quickstart URL parsing section why: The branch added a runnable "Parse URLs" section to the quickstart that the existing Documentation entries did not record. what: - Add "Quickstart now demonstrates URL parsing" entry under the Documentation section, citing GitURL and the url-parsing guide --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 18e5c31b9..ed81d385c 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,12 @@ upstream documentation. The {mod}`libvcs.url.registry` guide untangles its examples into narrated, runnable steps, and the module hub pages open with what each layer does. +#### Quickstart now demonstrates URL parsing (#540) + +The quickstart's "Basic Usage" adds a "Parse URLs" section that detects +and parses a repository URL with {class}`~libvcs.url.git.GitURL`. See +{ref}`url-parsing` for the full tour. + ## libvcs 0.45.0 (2026-06-28) libvcs 0.45.0 makes test behavior independent of execution order by removing shared state from the URL registry and the pytest fixtures. {class}`~libvcs.url.registry.VCSRegistry` now keeps its parsers per instance, so building a custom registry no longer mutates the module-level `registry`, and the `git_repo`, `hg_repo`, and `svn_repo` fixtures hand every test its own clone instead of a shared checkout — fixing cross-test state leakage for downstream test suites such as vcspull. Contributors also gain an opt-in parallel test mode via pytest-xdist.