Skip to content

fix(marketplace): record the commit an annotated tag points to - #3161

Open
Rodion Kazennov (nefayran) wants to merge 2 commits into
microsoft:mainfrom
nefayran:fix/3048-annotated-tag-sha
Open

Rodion Kazennov (nefayran) wants to merge 2 commits into
microsoft:mainfrom
nefayran:fix/3048-annotated-tag-sha

Conversation

@nefayran

@nefayran Rodion Kazennov (nefayran) commented Oct 3, 2026 •

Copy link
Copy Markdown

Description

git ls-remote lists an annotated or signed tag twice: the tag object under refs/tags/<name> and the commit it points to under refs/tags/<name>^{}. _parse_ls_remote_output in src/apm_cli/marketplace/ref_resolver.py dropped the ^{} line, so apm pack wrote the tag object as source.sha. A checkout of the tag lands on the commit, and installers that compare the two reject the pin, as in the caveman case in the issue.

Both parsers now read tags through tag_commit_shas in src/apm_cli/deps/git_remote_ops.py, which gives a tag the SHA from its ^{} line; that line adds no ref of its own. It is registered as the owner ls-remote-tag-commits, with the static guard transport-platform-ls-remote-tag-commits. Lightweight tags and branches keep their only SHA. A ref: that is a full SHA never reaches this parser, so exact pins are unchanged. resolve_ref_sha uses the marketplace parser and is only called with HEAD, which has no ^{} line.

Issue and approved scope

Issue: #3048

Human scope-approval comment: #3048 (comment)

This PR completes the issue. A marketplace.json packed before the fix keeps the tag object SHA until apm pack runs again.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

tests/unit/marketplace/test_ref_resolver.py: test_peeled_tag_skipped is now test_annotated_tag_takes_peeled_commit_sha and expects the commit SHA. New cases cover a ^{} line that comes before its tag line, a branch with a lightweight and an annotated tag, and a ^{} line with no tag line.

tests/unit/marketplace/test_annotated_tag_source_sha.py creates the tags with git in a local bare repository (LocalGitRepositoryFactory): annotated, lightweight, and SSH-signed (skipped when ssh-keygen is missing). It packs a marketplace with ref: v1.0.0, version: "~1.0.0" and ref: v1.1.0 entries, then clones the repository, checks out each source.ref and compares HEAD with source.sha, the same check an installer makes. With the old parser, six of the new tests fail. tests/unit/deps/test_git_remote_ops.py covers tag_commit_shas directly, and the guard has its case in tests/integration/test_architecture_owner_rule_mutations.py. On 20,000 random ls-remote outputs parse_ls_remote_output returns the same refs before and after the change.

Locally uv run pytest tests/unit tests/test_console.py passes (23585 passed, 42 skipped, 21 xfailed). Ruff check and format, the pylint R0801 duplication check, scripts/lint-auth-signals.sh and scripts/lint-architecture-boundaries.sh are clean.

Spec conformance (OpenAPM v0.1)

  • Spec edit: docs/src/content/docs/specs/openapm-v0.1.md updated
    (new/changed <a id="req-XXX"></a> anchor + prose + Appendix C
    row).
  • Manifest edit: docs/src/content/docs/specs/manifests/openapm-v0.1.requirements.yml
    updated.
  • Test edit: a @pytest.mark.req("req-XXX") test under
    tests/spec_conformance/ added or extended.
  • CONFORMANCE.{md,json} regenerated via
    uv run --extra dev python -m tests.spec_conformance.gen_statement
    and committed.
  • N/A -- this PR does not change OpenAPM-observable behaviour.

`git ls-remote` lists an annotated or signed tag twice: the tag object
under `refs/tags/<name>` and its commit under `refs/tags/<name>^{}`.
`_parse_ls_remote_output` dropped the `^{}` line, so `apm pack` wrote the
tag object as `source.sha`, and installers that check out the tag and
compare the result rejected the pin. The tag now takes the peeled SHA, as
`parse_ls_remote_output` in `deps/git_remote_ops.py` already does.
Lightweight tags and branches keep their only SHA.

Fixes microsoft#3048

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Peeled-tag interpretation duplicates an existing durable decision instead of routing through one canonical owner.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Fixes marketplace packing so annotated and signed tags record the checked-out commit SHA rather than the tag-object SHA.

Changes:

  • Handles peeled ^{} tag references.
  • Adds parser and local-repository regression tests.
  • Documents the fix in the changelog.
File Description
src/​apm_cli/​marketplace/​ref_resolver.py Applies peeled commit SHAs to tags.
tests/​unit/​marketplace/​test_ref_resolver.py Tests parser edge cases.
tests/​unit/​marketplace/​test_annotated_tag_source_sha.py Tests real tags and packed output.
CHANGELOG.md Records the fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/apm_cli/marketplace/ref_resolver.py Outdated
Comment on lines +193 to +197
if refname.endswith("^{}"):
peeled[refname[:-3]] = sha
continue
refs.append(RemoteRef(name=refname, sha=sha))
return refs
return [RemoteRef(name=ref.name, sha=peeled.get(ref.name, ref.sha)) for ref in refs]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f8859f2. tag_commit_shas in deps/git_remote_ops.py now decides which commit a tag record names, and the annotated-tag security note moved with it. parse_ls_remote_output and the marketplace parser both read tags through it, so marketplace/ref_resolver.py no longer handles ^{} itself.

The owner is registered as ls-remote-tag-commits in transport-auth-platform.json, guarded by transport-platform-ls-remote-tag-commits (one definition of tag_commit_shas, no "^{}" literal in src/apm_cli outside the owner), with its mutation case. On 20,000 random ls-remote outputs, parse_ls_remote_output returns the same refs before and after the change.

The marketplace parser had its own reading of peeled `^{}` records next
to the one in `deps/git_remote_ops.py`. `tag_commit_shas` in
`git_remote_ops.py` now decides which commit a tag record names, with
the annotated-tag security note moved along; `parse_ls_remote_output`
and the marketplace `_parse_ls_remote_output` both read tags through
it, and the marketplace module no longer mentions `^{}`.

The owner is registered as `ls-remote-tag-commits` with the guard
`transport-platform-ls-remote-tag-commits`: one definition of
`tag_commit_shas`, and no `"^{}"` literal in `src/apm_cli` outside the
owner. The guard has its mutation case, and `tag_commit_shas` has
direct unit tests.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants