package git import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "gopkg.in/src-d/go-git.v4/core" ) // Tag represents an annotated tag object. It points to a single git object of // any type, but tags typically are applied to commit or blob objects. It // provides a reference that associates the target with a tag name. It also // contains meta-information about the tag, including the tagger, tag date and // message. // // https://git-scm.com/book/en/v2/Git-Internals-Git-References#Tags type Tag struct { Hash core.Hash Name string Tagger Signature Message string TargetType core.ObjectType Target core.Hash r *Repository } // Type returns the type of object. It always returns core.TreeObject. /* func (t *Tag) Type() core.ObjectType { return core.TagObject } */ // ID returns the object ID of the tag, not the object that the tag references. // The returned value will always match the current value of Tag.Hash. // // ID is present to fulfill the Object interface. func (t *Tag) ID() core.Hash { return t.Hash } // Type returns the type of object. It always returns core.TagObject. // // Type is present to fulfill the Object interface. func (t *Tag) Type() core.ObjectType { return core.TagObject } // Decode transforms a core.Object into a Tag struct. func (t *Tag) Decode(o core.Object) (err error) { if o.Type() != core.TagObject { return ErrUnsupportedObject } t.Hash = o.Hash() reader, err := o.Reader() if err != nil { return err } defer checkClose(reader, &err) r := bufio.NewReader(reader) for { line, err := r.ReadSlice('\n') if err != nil && err != io.EOF { return err } line = bytes.TrimSpace(line) if len(line) == 0 { break // Start of message } split := bytes.SplitN(line, []byte{' '}, 2) switch string(split[0]) { case "object": t.Target = core.NewHash(string(split[1])) case "type": t.TargetType, err = core.ParseObjectType(string(split[1])) if err != nil { return err } case "tag": t.Name = string(split[1]) case "tagger": t.Tagger.Decode(split[1]) } if err == io.EOF { return nil } } data, err := ioutil.ReadAll(r) if err != nil { return err } t.Message = string(data) return nil } // Encode transforms a Tag into a core.Object. func (t *Tag) Encode(o core.Object) error { o.SetType(core.TagObject) w, err := o.Writer() if err != nil { return err } defer checkClose(w, &err) if _, err = fmt.Fprintf(w, "object %s\ntype %s\ntag %s\ntagger ", t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil { return err } if err = t.Tagger.Encode(w); err != nil { return err } if _, err = fmt.Fprint(w, "\n\n"); err != nil { return err } if _, err = fmt.Fprint(w, t.Message); err != nil { return err } return err } // Commit returns the commit pointed to by the tag. If the tag points to a // different type of object ErrUnsupportedObject will be returned. func (t *Tag) Commit() (*Commit, error) { if t.TargetType != core.CommitObject { return nil, ErrUnsupportedObject } return t.r.Commit(t.Target) } // Tree returns the tree pointed to by the tag. If the tag points to a commit // object the tree of that commit will be returned. If the tag does not point // to a commit or tree object ErrUnsupportedObject will be returned. func (t *Tag) Tree() (*Tree, error) { // TODO: If the tag is of type commit, follow the commit to its tree? switch t.TargetType { case core.CommitObject: commit, err := t.r.Commit(t.Target) if err != nil { return nil, err } return commit.Tree() case core.TreeObject: return t.r.Tree(t.Target) default: return nil, ErrUnsupportedObject } } // Blob returns the blob pointed to by the tag. If the tag points to a // different type of object ErrUnsupportedObject will be returned. func (t *Tag) Blob() (*Blob, error) { if t.TargetType != core.BlobObject { return nil, ErrUnsupportedObject } return t.r.Blob(t.Target) } // Object returns the object pointed to by the tag. func (t *Tag) Object() (Object, error) { return t.r.Object(t.Target, t.TargetType) } // String returns the meta information contained in the tag as a formatted // string. func (t *Tag) String() string { return fmt.Sprintf( "%s %s\nObject: %s\nType: %s\nTag: %s\nTagger: %s\nDate: %s\n", core.TagObject, t.Hash, t.Target, t.TargetType, t.Name, t.Tagger.String(), t.Tagger.When, ) } // TagIter provides an iterator for a set of tags. type TagIter struct { core.ObjectIter r *Repository } // NewTagIter returns a TagIter for the given repository and underlying // object iterator. // // The returned TagIter will automatically skip over non-tag objects. func NewTagIter(r *Repository, iter core.ObjectIter) *TagIter { return &TagIter{iter, r} } // Next moves the iterator to the next tag and returns a pointer to it. If it // has reached the end of the set it will return io.EOF. func (iter *TagIter) Next() (*Tag, error) { obj, err := iter.ObjectIter.Next() if err != nil { return nil, err } tag := &Tag{r: iter.r} return tag, tag.Decode(obj) } // ForEach call the cb function for each tag contained on this iter until // an error happends or the end of the iter is reached. If ErrStop is sent // the iteration is stop but no error is returned. The iterator is closed. func (iter *TagIter) ForEach(cb func(*Tag) error) error { return iter.ObjectIter.ForEach(func(obj core.Object) error { tag := &Tag{r: iter.r} if err := tag.Decode(obj); err != nil { return err } return cb(tag) }) }