feat(tools/sourcehut, tools/vcs): add SourceHut forge bridge and implement Mercurial backend#670
feat(tools/sourcehut, tools/vcs): add SourceHut forge bridge and implement Mercurial backend#670onlyarnav wants to merge 11 commits into
Conversation
Generated-by: Antigravity
Generated-by: Antigravity
Generated-by: Antigravity
Generated-by: Antigravity
Generated-by: Antigravity
Generated-by: Antigravity
59e01f8 to
7b3880b
Compare
onlyarnav
left a comment
There was a problem hiding this comment.
All the changes suggested by GitHub Actions bot have been implemented, now working on CI pipeline failing
Generated-by: Antigravity
7b3880b to
bdb110f
Compare
|
Thanks for the contribution. I ran the PR review skill over it, and there are a couple of minor things you might want to fix: A scattering of minor/nit observations (one markdown-table break in the docs, two hg-backend contract-parity concerns, two sourcehut nits), none of which gate the merge. Approach looks solid on both fronts — the SourceHut bridge is clean stdlib-only code with real tests, and the Mercurial backend fills in a genuine extension point with matching @hg_required tests. A few observations below, none blocking; the one I'd most like a second look at is a markdown table that the docs hunk appears to break. A blank line terminates a GFM table, so the "Project governance" row renders as loose text below a truncated table. The lychee and doctoc hooks won't flag this (not a link or TOC entry), so it needs a manual fix — remove the inserted blank line. While you're in this file, the earlier hunk rewriting the "non-Git/non-Hg systems" list also leaves a stray blank line before [Jujutsu] that splits the sentence; worth tidying in the same pass. tools/vcs/src/magpie_vcs/init.py:1372 — create_branch maps to hg bookmark , but the abstract contract is "Create and switch to a new line of work" and the Git binding switches (git checkout -b). hg bookmark creates/activates a bookmark without moving the working directory the way a branch checkout does, and the new test commits on the same parent so it doesn't exercise the "switched to a different line of work" semantics. Not a definite bug — flagging for a maintainer with hg expertise. tools/vcs/src/magpie_vcs/init.py:1396 — reset_worktree uses hg purge --all, which removes .hgignore'd files too, whereas the Git binding's git clean -fd deletes only untracked files. If the per-run reset is meant to preserve ignored build/cache artefacts, drop --all. There's also no @hg_required test for reset_worktree (the git backend has one), so this difference is unverified. tools/sourcehut/src/magpie_sourcehut/cli.py:496 — ticket label with neither --add nor --remove is a silent no-op that still prints the ticket and exits 0. Consider requiring at least one (argparse required-mutually-exclusive group), or a "nothing to do" note. tools/sourcehut/README.md:141 — Features says the VCS integration "Reads repo metadata and refs", but repo.py's query pulls only id/name/description/visibility/updated — no refs. Tighten the wording or add refs to the query. This review was drafted by an AI-assisted tool. The findings below are observations, not blockers; an Apache Magpie maintainer — a real person — will take the next look at the PR. If you think a finding is mis-applied, please reply on the PR and a maintainer will weigh in. |
Generated-by: Antigravity
Generated-by: Antigravity
…ation Generated-by: Antigravity
potiuk
left a comment
There was a problem hiding this comment.
Thanks for this — a well-structured, dependency-free bridge and a clean Mercurial backend, and nice quick turnaround on the earlier fixes. The top item below is CI-blocking after your merge with main; the rest are mostly medium/low. Two doc items land on lines this PR didn't change, so I couldn't anchor them inline:
tools/vcs/README.md:92still listshgas “extension point | operations raise a clear error → #601”, contradicting the working backend this PR adds (the module docstring attools/vcs/src/magpie_vcs/__init__.pyalso still calls Mercurial an extension point).docs/vendor-neutrality.md:208(Tool adapters table) still lists Mercurial only as an extension point, while L163/L396/L488 in the same file now say “Mercurial (complete)”.
|
|
||
| # SourceHut (sr.ht) Forge Bridge | ||
|
|
||
| **Capability:** contract:tracker + contract:source-control + contract:mail-archive |
There was a problem hiding this comment.
Heads-up: after the merge with main this now breaks CI. tools/sourcehut declares contract:tracker + contract:source-control + contract:mail-archive, but main recently gained tools/vendor-neutrality-score (#672), which requires every contract tool's README to declare **Kind:** and **Vendor:**. Without them its loader raises ValueError and the vendor-neutrality-score pytest job fails. Please add, e.g.:
**Kind:** implementation
**Vendor:** SourceHut
(Bonus: that also makes SourceHut count as an additional backend for tracker / source-control / mail-archive in the neutrality score.)
| if err_errors: | ||
| err_msgs = [e.get("message", "Unknown error") for e in err_errors] | ||
| raise SourceHutError(f"HTTP {exc.code}: {'; '.join(err_msgs)}") | ||
| except Exception: |
There was a problem hiding this comment.
The inner except Exception: pass catches the raise SourceHutError(...) two lines above, so the parsed GraphQL error messages are always discarded and callers fall through to the generic HTTP request … failed with status N (L85). A 400 with {"errors":[{"message":"invalid tracker"}]} surfaces only the status, never the message. Suggest building the message string inside the try and raising after it, or narrowing the inner except to (json.JSONDecodeError, KeyError, TypeError).
| if ns.subcommand == "graphql": | ||
| vars_dict = None | ||
| if ns.variables: | ||
| vars_dict = json.loads(ns.variables) |
There was a problem hiding this comment.
main() has no error handling: json.loads(ns.variables) raises JSONDecodeError on malformed --variables, and any SourceHutError from the calls below escapes as a raw traceback rather than a clean message. Consider wrapping the dispatch in try/except (SourceHutError, json.JSONDecodeError) as e: → print(f"magpie-sourcehut: {e}", file=sys.stderr); return 2.
| if base: | ||
| args.extend(["-r", base]) | ||
| if paths: | ||
| args.extend(paths) |
There was a problem hiding this comment.
diff (here) and log (L414) append paths without a -- terminator, unlike GitBackend (L232/L253) and this class's own stage (L426). A path beginning with -, or named like an hg flag, is parsed as an option → wrong output or an hg error. Suggest args.extend(["--", *paths]) in both.
| emails = [edge.get("node") for edge in edges if edge.get("node")] | ||
|
|
||
| # Sort emails by date if possible | ||
| with contextlib.suppress(Exception): |
There was a problem hiding this comment.
contextlib.suppress(Exception) swallows any sort failure, and then emails[0] is used as the cover letter — a malformed node would silently yield the wrong description/author. Narrow to (TypeError, AttributeError), or validate the nodes before sorting.
| mlist = res.get("list") or {} | ||
| patches_conn = mlist.get("patches") or {} | ||
| edges = patches_conn.get("edges") or [] | ||
| return [edge.get("node") for edge in edges if edge.get("node")] |
There was a problem hiding this comment.
edge.get("node") assumes every edge is a dict; a null edge in the connection raises AttributeError. Same pattern at L122 and the patches loop at L147. Cheap to guard (if edge / if patch is not None).
Summary
tools/sourcehutas a forge bridge: Implements ticket tracking (todo.sr.ht), mailing list patchset review (lists.sr.ht), CI build status (builds.sr.ht), and repository reads (git.sr.ht/hg.sr.ht) using SourceHut's GraphQL APIs.MercurialBackendintools/vcs: Replaces the Mercurial (hg) stub with a complete, fully functional command-line wrapper backend supporting bookmarks, staging, commits, diffs, logs, and worktree purging.tools/sourcehutcapabilities (contract:tracker + contract:source-control + contract:mail-archive) and marks Mercurial support as complete.Type of change
.claude/skills/<name>/) — eval fixtures updated belowtools/<system>/*.md)tools/*/withpyproject.toml)docs/,README.md,CONTRIBUTING.md)projects/_template/)prek, workflows, validators)Test plan
prek run --all-filespasses (verified with local pre-flight checks, skipping link checker hooklycheewhich requires MSVC toolchain to build from source on Windows).uv run pytest/ruff check/mypypassespytestontools/vcs(19 passed, 3 skipped becausehgis not installed on the local system).pytestontools/sourcehut(13 passed offline using mocked HTTP calls).skill-and-tool-validatedirectly; all validations passed cleanly with 0 hard schema errors.RFC-AI-0004 compliance
<PROJECT>,<tracker>,<upstream>,<security-list>) used in all skill / tool prose (verified via validator).Linked issues
Refs #601 (Mercurial support), Refs #607 (SourceHut integration).
Notes for reviewers (optional)
hgCLI. The tests gracefully skip ifhgis not installed locally.urllibto keep the CLI wrapper dependency-free.lists.sr.htare mapped into standard pull requests by treating the cover letter email as the description, individual patches as commits, and mailing list replies as review comments.