Search code examples
libgit2libgit2sharp

What's the fastest way to find Tags pointing to Commits?


With libgit2sharp I would like to do the following:

foreach( Commit commit in repo.Commits )
{
    // How to implement assignedTags?
    foreach( Tag tag in commit.assignedTags ) {}
}

I want to get all tags assigned to the current commit. Whats the best way to do that? Iterate through all Tags and see if tag.Target.Sha == commit.Sha? Thats not very performant. Is there another way?


Solution

  • So I want to get all tags assigned to the current commit. Whats the best way to do that? Iterate through all Tags and see if tag.Target.Sha == commit.Sha? Thats not very performant. Is there another way?

    There are two things to take into account when it comes to Tags.

    • A Tag can point to something else than a Commit (A Tree or a Blob, for instance)
    • A Tag can point to another Tag (chained annotated tags)

    The code below should fit your need by taking these points above into account.

    Note: repo.Commits will only enumerate the commits reachable from the current branch (HEAD). The code below extends this to easily browse all the reachable commits.

    ...
    
    using (var repo = new Repository("Path/to/your/repo"))
    {
        // Build up a cached dictionary of all the tags that point to a commit
        var dic = TagsPerPeeledCommitId(repo);
    
        // Let's enumerate all the reachable commits (similarly to `git log --all`)
        foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter {Since = repo.Refs}))
        {
            foreach (var tags in AssignedTags(commit, dic))
            {
                Console.WriteLine("Tag {0} points at {1}", tags.Name, commit.Id);
            }
        }
    }
    
    ....
    
    private static IEnumerable<Tag> AssignedTags(Commit commit, Dictionary<ObjectId, List<Tag>> tags)
    {
        if (!tags.ContainsKey(commit.Id))
        {
            return Enumerable.Empty<Tag>();
        }
    
        return tags[commit.Id];
    }
    
    private static Dictionary<ObjectId, List<Tag>> TagsPerPeeledCommitId(Repository repo)
    {
        var tagsPerPeeledCommitId = new Dictionary<ObjectId, List<Tag>>();
    
        foreach (Tag tag in repo.Tags)
        {
            GitObject peeledTarget = tag.PeeledTarget;
    
            if (!(peeledTarget is Commit))
            {
                // We're not interested by Tags pointing at Blobs or Trees
                continue;
            }
    
            ObjectId commitId = peeledTarget.Id;
    
            if (!tagsPerPeeledCommitId.ContainsKey(commitId))
            {
                // A Commit may be pointed at by more than one Tag
                tagsPerPeeledCommitId.Add(commitId, new List<Tag>());
            }
    
            tagsPerPeeledCommitId[commitId].Add(tag);
        }
    
        return tagsPerPeeledCommitId;
    }