gitgogo-git

Get Tags of specific Branch


Using go-git : is there a way to get only (lightweight and annotated) tags of a specific branch ?

As I’m mainly interested in the tags of the master branch, something like git tag --merged would be sufficient too.

It doesn’t seem to be possible with the basic go-git methods like Tags()...


Solution

  • Not exactly a short solution, but the following code achieved the goal by:

    1. Read the commit hashes of the entire branch.
    2. Read all the tags of the repository.
    3. Check and only print the tag of which the hash is in the branch.

    Note: Didn't try with annotated tag yet. But it should be close.

    package main
    
    import (
        "log"
    
        "github.com/src-d/go-billy/memfs"
        "gopkg.in/src-d/go-git.v4"
        "gopkg.in/src-d/go-git.v4/plumbing"
        "gopkg.in/src-d/go-git.v4/plumbing/object"
        "gopkg.in/src-d/go-git.v4/storage/memory"
    )
    
    func getBranchHashes(repo *git.Repository, branchName string) (hashes map[plumbing.Hash]bool, err error) {
    
        // get branch reference name
        branch, err := repo.Branch("master")
        if err != nil {
            return
        }
    
        // get reference of the reference name
        ref, err := repo.Reference(branch.Merge, true)
        if err != nil {
            return
        }
    
        // retrieve logs from the branch reference commit
        // (default order is depth first)
        logs, err := repo.Log(&git.LogOptions{
            From: ref.Hash(),
        })
        if err != nil {
            return
        }
        defer logs.Close()
    
        // a channel to collect all hashes
        chHash := make(chan plumbing.Hash)
        chErr := make(chan error)
        go func() {
            err = logs.ForEach(func(commit *object.Commit) (err error) {
                chHash <- commit.Hash
                return
            })
            if err != nil {
                chErr <- err
            }
            close(chErr)
            close(chHash)
        }()
    
        // make all hashes into a map
        hashes = make(map[plumbing.Hash]bool)
    hashLoop:
        for {
            select {
            case err = <-chErr:
                if err != nil {
                    return
                }
                break hashLoop
            case h := <-chHash:
                hashes[h] = true
            }
        }
        return
    }
    
    type TagRef struct {
        Hash plumbing.Hash
        Name string
    }
    
    func main() {
        // Filesystem abstraction based on memory
        fs := memfs.New()
    
        // Git objects storer based on memory
        storer := memory.NewStorage()
    
        // Clones the repository into the worktree (fs) and storer all the .git
        // content into the storer
        repo, err := git.Clone(storer, fs, &git.CloneOptions{
            URL: "https://github.com/yookoala/gofast.git",
        })
        if err != nil {
            log.Fatal(err)
        }
    
        hashes, err := getBranchHashes(repo, "master")
        if err != nil {
            log.Fatal(err)
        }
    
        // get all tags in the repo
        tags, err := repo.Tags()
        if err != nil {
            log.Fatal(err)
        }
    
        tagRefs := make(chan TagRef)
        go func() {
            err = tags.ForEach(func(ref *plumbing.Reference) (err error) {
                if annotedTag, err := repo.TagObject(ref.Hash()); err != plumbing.ErrObjectNotFound {
                    if annotedTag.TargetType == plumbing.CommitObject {
                        tagRefs <- TagRef{
                            Hash: annotedTag.Target,
                            Name: ref.Name().Short(),
                        }
                    }
                    return nil
                }
                tagRefs <- TagRef{
                    Hash: ref.Hash(),
                    Name: ref.Name().Short(),
                }
                return
            })
            if err != nil {
                log.Fatal(err)
            }
    
            close(tagRefs)
        }()
    
        for tagRef := range tagRefs {
            if _, ok := hashes[tagRef.Hash]; ok {
                log.Printf("tag: %s, hash: %s", tagRef.Name, tagRef.Hash)
            }
        }
    }