pygit2 icon indicating copy to clipboard operation
pygit2 copied to clipboard

tag objects from references

Open imbuedhope opened this issue 6 years ago • 12 comments
trafficstars

It looks like it is currently impossible to access the Tag object from a Reference that is a tag.

The C API provides git_reference_is_tag and git_tag_lookup, but there doesn't seem to be an equivalent functionality in pygit2. Searching for git_tag_* functions in src/ suggests that there are no hooks for the git_tag_* functions.

Currently Tag is a subclass of Object. This doesn't really make sense. Unlike Blob, Tree, and Commit a Tag is a reference to a Commit. Also, the docs for git_tag_tagger leans toward Tag.Tagger object to just be of type Signature instead.

It makes sense to make Tag a subclass of Reference similar to Branch, remove Tag.Tagger (and replace with the already existing Signature, changing Repository.create_tag to match), and have Repository.References.get() return a Tag object where appropriate.

imbuedhope avatar Dec 31 '18 20:12 imbuedhope

Our Tag object is an annotated tag, so it's correct that it inherits from Object.

So what you propose is to add a new object for regular tags, which would inherit from Reference.

We may call it Tag and rename our Tag to AnnotatedTag or TagObject or something else. The issue here is with backwards compatibility, so maybe we should call the new object something like ReferenceTag or TagReference.

Alternatively we could keep it simple and just add Reference.is_tag

Unless I'm mistaken libgit2's git_tag, git_tag_lookup and most other git_tag_* functions are all about annotated tags, not reference tags. Apparently one exception is git_tag_list which returns a list of tag names.

jdavid avatar Jan 01 '19 10:01 jdavid

Ah, so that's why it sub classed Object. It looks like you're right, I was a bit confused on the behavior of tags in git.

Looking at it again the behavior is more so something like, some Reference objects are tags. Some tags are annotated and point to a Tag object while other tags are simple references that point to a Commit object instead.

Basically, this resulted in some confusion on my part.

If peel() works appropriately I think adding a Reference.is_tag or more descriptively Reference.is_annotated_tag should suffice.

So maybe support for the following?

import pygit2
repo = pygit2.Repository('.')
for ref in repo.references:
    if ref.is_tag:
        tag = ref.peel(pygit2.Tag)
        # do stuff with tag

It may make sense to also add is_commit, is_tree, and is_blob fields too.

imbuedhope avatar Jan 01 '19 12:01 imbuedhope

Not exactly.

From libgit2, https://libgit2.org/libgit2/#HEAD/group/reference/git_reference_is_tag

1 when the reference lives in the refs/tags namespace; 0 otherwise.

For a reference tag that points to a commit Reference.is_tag will return true, but peeling it to pygit2.Tag will raise an error.

Right now you could write that code like this:

import pygit2
repo = pygit2.Repository('.')
for ref in repo.references:
    obj = repo[ref.target]
    if isinstance(obj, pygit2.Tag):
        tag = obj
        # do stuff with tag

The test could also be written as obj.type == pygit2.GIT_OBJ_TAG

So it would be nice to add Reference.is_tag; maybe a shorthand for repo[ref.target], e.g ref.get(); and maybe shorthands for the test (obj.is_tag etc.)

jdavid avatar Jan 02 '19 18:01 jdavid

By the way I just removed Reference.get_object() which has been deprecated for over 4 years.

jdavid avatar Jan 02 '19 18:01 jdavid

Isn't Reference.peel(None) equivalent to Repository[Reference.target]? It would still be useful to have a Reference.get() for better readability, but it would functionally be the same thing.

I agree with a Reference.is_tag. I feel like it makes sense to also add similar hooks for Reference.is_branch, and Reference.is_remote, and Reference.is_note, mapping to git_reference_is_branch, git_reference_is_remote, and git_reference_is_note respectively.

Although for the sake of consistency with how Object works it might be better to just have a Reference.type.

Maybe Reference.obj_type as short hand for repo[ref.target].type so the decision to allocate an object can be made based on the type (which would be a tiny efficiency boost).

imbuedhope avatar Jan 02 '19 19:01 imbuedhope

Not really, if the tag points to a tag object such as tag -> tag object -> commit then Reference.peel(None) will return the commit object, while Repository[Reference.target] will return the tag object. Also, for symbolic references such as head Repository[Reference.target] will raise an error.

+1 to Reference.is_xxx, better to just map the libgit2 api.

-1 for Reference.obj_type, if we had to do something like that I would rather write Reference.get().type or maybe Reference.object.type ; but if in doubt it's probably better not to implement

jdavid avatar Jan 03 '19 09:01 jdavid

I think a short hand for repo[ref.target] in the form of Reference.get() or Reference.object has merit since it would mean not having to pass a Repository object around as a parameter in functions.

Reference.get() seems more in line with Reference.peel() and Reference.resolve() when compared to Reference.object

imbuedhope avatar Jan 03 '19 15:01 imbuedhope

okay to .get()

It would be equivalent to:

repo[ref.resolve().target]

jdavid avatar Jan 06 '19 09:01 jdavid

+1 for this feature.

Related, it would be nice if there was a mapping of to reference. ie: What is stored in .git/packed-refs

dsully avatar May 15 '19 01:05 dsully

@jdavid @imbuedhope is there any progress or estimations for this feature?

I'm trying to get a tag attached to commit somehow but this solutions do not work at the moment: https://github.com/libgit2/pygit2/issues/856#issuecomment-450724765 https://github.com/libgit2/pygit2/issues/856#issuecomment-450940921

please explain how to get the tag for current commit

casualuser avatar Nov 03 '21 13:11 casualuser

@jdavid @imbuedhope is there any progress or estimations for this feature?

I'm trying to get a tag attached to commit somehow but this solutions do not work at the moment: #856 (comment) #856 (comment)

please explain how to get the tag for current commit

A workaround based on what David suggested worked for me, and it didn't seem like there was a pressing need for this so I haven't looked at this in a while.

Your issue seems a bit off topic towards the discussion here (this thread pertains to getting objects from tags not tags that match an object), that said...

By tag for current commit, do you mean a (list of) tag(s) pointing to a specific commit?

If so, you'll probably have to loop over all the refs from pygit2.Repository.references, find the ref target, get to the object they point to, and check manually if the oid matches that of the commit. Tags point to their commits, but commits don't point to their tags.

imbuedhope avatar Nov 30 '21 18:11 imbuedhope

My approach to this.

In [13]: from pygit2 import Repository as Repo

In [14]: from pygit2 import Commit

In [15]: tag_commits = []
    ...: repo = Repo('.')
    ...: for ref in repo.references:
    ...:     if ref.startswith('refs/tags/'):
    ...:         commit = repo.lookup_reference(ref).peel(Commit)
    ...:         tag_commits.append(commit)
    ...:

In [16]: tag_commits
Out[16]:
[<pygit2.Object{commit:fa836018981d5253ae20524d7220b295d927d902}>,
 <pygit2.Object{commit:fa836018981d5253ae20524d7220b295d927d902}>,
 <pygit2.Object{commit:fa836018981d5253ae20524d7220b295d927d902}>]

Spitfire1900 avatar Mar 27 '24 03:03 Spitfire1900