Skip to content

Add a first-class API to get the latest tag from a remote via git ls-remote#2119

Open
huyhoang171106 wants to merge 1 commit intogitpython-developers:mainfrom
huyhoang171106:feat/add-a-first-class-api-to-get-the-latest-
Open

Add a first-class API to get the latest tag from a remote via git ls-remote#2119
huyhoang171106 wants to merge 1 commit intogitpython-developers:mainfrom
huyhoang171106:feat/add-a-first-class-api-to-get-the-latest-

Conversation

@huyhoang171106
Copy link
Copy Markdown

Summary

Introduce a dedicated convenience method on Git for the issue use case (querying a remote URL/repo and returning its newest tag). This avoids requiring users to manually run ls_remote(...) and parse tab-delimited output themselves. The method should encapsulate stable parsing behavior and edge-case handling (empty output, annotated tag peeled refs, malformed lines).

Files changed

  • git/cmd.py (modified)

Testing

  • Not run in this environment.

Closes #1071

Copilot AI review requested due to automatic review settings March 26, 2026 16:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a first-class convenience API on git.cmd.Git to fetch the newest tag from a remote by running git ls-remote and parsing its output, so callers don’t need to manually parse tab-delimited results.

Changes:

  • Add Git.latest_remote_tag(repository) that calls ls-remote --tags --sort=-version:refname.
  • Parse ls-remote output to return the first valid tag, handling empty output, malformed lines, and peeled ^{} annotated-tag refs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +998 to +999
def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
output = self.ls_remote("--tags", "--sort=-version:refname", repository)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

repository is passed as a positional argument without a -- separator. If the value begins with -, git will treat it as an option (option injection), which is especially problematic for commands like ls-remote (e.g., it could alter config/transport behavior). Consider inserting -- before repository and also mirroring other URL-taking APIs by rejecting unsafe ext::-style transports by default (with an allow_unsafe_protocols escape hatch).

Suggested change
def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
output = self.ls_remote("--tags", "--sort=-version:refname", repository)
def latest_remote_tag(
self,
repository: PathLike,
allow_unsafe_protocols: bool = False,
) -> Optional[str]:
repo_str = os.fspath(repository)
if repo_str.startswith("ext::") and not allow_unsafe_protocols:
raise UnsafeProtocolError(
f"Refusing to use unsafe protocol for repository {repo_str!r}"
)
output = self.ls_remote("--tags", "--sort=-version:refname", "--", repo_str)

Copilot uses AI. Check for mistakes.
_warn_use_shell(extra_danger=False)
return super().__getattribute__(name)

def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This new public convenience method has no docstring, but nearby public methods in Git are documented. Please add a docstring clarifying what “latest” means (it relies on --sort=-version:refname), how annotated tags are handled, and when None is returned.

Suggested change
def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
"""Return the name of the latest tag from a remote repository.
The remote tags are obtained via :meth:`ls_remote` with
``--tags`` and ``--sort=-version:refname``. This means that tags
are sorted in descending order according to their version-like
refname, and the first suitable entry is considered the "latest".
Annotated tags are handled by normalizing peeled references:
``ls-remote --tags`` may return both ``refs/tags/<tag>`` and
``refs/tags/<tag>^{}; this method strips the ``^{}`
suffix so that the underlying tag name ``<tag>`` is returned.
:param repository: Remote repository to query, as accepted by
:meth:`ls_remote` (for example, a URL or remote name).
:return: The name of the latest tag according to
``--sort=-version:refname``, or ``None`` if the remote has no
tags or no tag references can be parsed from the command
output.
"""

Copilot uses AI. Check for mistakes.
Comment on lines +998 to +1018
def latest_remote_tag(self, repository: PathLike) -> Optional[str]:
output = self.ls_remote("--tags", "--sort=-version:refname", repository)
if not output:
return None

for line in output.splitlines():
if not line:
continue
try:
_, ref = line.split("\t", 1)
except ValueError:
continue
if not ref.startswith("refs/tags/"):
continue
tag = ref[len("refs/tags/") :]
if tag.endswith("^{}"):
tag = tag[:-3]
if tag:
return tag

return None
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

This introduces new parsing/selection behavior (handling empty output, malformed lines, and peeled ^{} refs) but there are no tests covering it. Since the project already has extensive Git wrapper tests (e.g. test/test_git.py), please add unit tests that mock Git.execute/ls_remote output to cover: empty output, a peeled annotated tag appearing first, and malformed lines being ignored.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Getting the latest tag of a remote repo

2 participants