git-module

fork of https://git.lin.moe/gogs/git-module

git clone git://git.lin.moe/fork/git-module.git

  1package git
  2
  3import (
  4	"bytes"
  5	"context"
  6	"fmt"
  7	"strings"
  8)
  9
 10// parseTag parses tag information from the (uncompressed) raw data of the tag
 11// object. It assumes "\n\n" separates the header from the rest of the message.
 12func parseTag(data []byte) (*Tag, error) {
 13	tag := new(Tag)
 14	// we now have the contents of the commit object. Let's investigate.
 15	nextline := 0
 16l:
 17	for {
 18		eol := bytes.IndexByte(data[nextline:], '\n')
 19		switch {
 20		case eol > 0:
 21			line := data[nextline : nextline+eol]
 22			spacepos := bytes.IndexByte(line, ' ')
 23			reftype := line[:spacepos]
 24			switch string(reftype) {
 25			case "object":
 26				id, err := NewIDFromString(string(line[spacepos+1:]))
 27				if err != nil {
 28					return nil, err
 29				}
 30				tag.commitID = id
 31			case "type":
 32			case "tagger":
 33				sig, err := parseSignature(line[spacepos+1:])
 34				if err != nil {
 35					return nil, err
 36				}
 37				tag.tagger = sig
 38			}
 39			nextline += eol + 1
 40		case eol == 0:
 41			tag.message = string(data[nextline+1:])
 42			break l
 43		default:
 44			break l
 45		}
 46	}
 47	return tag, nil
 48}
 49
 50// getTag returns a tag by given object id.
 51func (r *Repository) getTag(ctx context.Context, id Oid) (*Tag, error) {
 52	t, ok := r.cachedTags.Get(id.String())
 53	if ok {
 54		logf("Cached tag hit: %s", id)
 55		return t.(*Tag), nil
 56	}
 57
 58	// Check tag type
 59	typ, err := r.CatFileType(ctx, id.String())
 60	if err != nil {
 61		return nil, err
 62	}
 63
 64	var tag *Tag
 65	switch typ {
 66	case ObjectCommit: // Tag is a commit
 67		tag = &Tag{
 68			typ:      ObjectCommit,
 69			id:       id,
 70			commitID: id,
 71			repo:     r,
 72		}
 73
 74	case ObjectTag: // Tag is an annotation
 75		data, err := exec(ctx, r.path, []string{"cat-file", "-p", id.String()}, nil)
 76		if err != nil {
 77			return nil, err
 78		}
 79
 80		tag, err = parseTag(data)
 81		if err != nil {
 82			return nil, err
 83		}
 84		tag.typ = ObjectTag
 85		tag.id = id
 86		tag.repo = r
 87	default:
 88		return nil, fmt.Errorf("unsupported tag type: %s", typ)
 89	}
 90
 91	r.cachedTags.Set(id.String(), tag)
 92	return tag, nil
 93}
 94
 95// TagOptions contains optional arguments for getting a tag.
 96//
 97// Docs: https://git-scm.com/docs/git-cat-file
 98type TagOptions struct {
 99	CommandOptions
100}
101
102// Tag returns a Git tag by given name, e.g. "v1.0.0".
103func (r *Repository) Tag(ctx context.Context, name string, opts ...TagOptions) (*Tag, error) {
104	var opt TagOptions
105	if len(opts) > 0 {
106		opt = opts[0]
107	}
108
109	refspec := RefsTags + name
110	refs, err := r.ShowRef(ctx, ShowRefOptions{
111		Tags:           true,
112		Patterns:       []string{refspec},
113		CommandOptions: opt.CommandOptions,
114	})
115	if err != nil {
116		return nil, err
117	} else if len(refs) == 0 {
118		return nil, ErrReferenceNotExist
119	}
120
121	id, err := NewIDFromString(refs[0].ID)
122	if err != nil {
123		return nil, err
124	}
125
126	tag, err := r.getTag(ctx, id)
127	if err != nil {
128		return nil, err
129	}
130	tag.refspec = refspec
131	return tag, nil
132}
133
134// TagsOptions contains optional arguments for listing tags.
135//
136// Docs: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---list
137type TagsOptions struct {
138	// SortKet sorts tags with provided tag key, optionally prefixed with '-' to sort tags in descending order.
139	SortKey string
140	// Pattern filters tags matching the specified pattern.
141	Pattern string
142	CommandOptions
143}
144
145// Tags returns a list of tags of the repository.
146func (r *Repository) Tags(ctx context.Context, opts ...TagsOptions) ([]string, error) {
147	var opt TagsOptions
148	if len(opts) > 0 {
149		opt = opts[0]
150	}
151
152	args := []string{"tag", "--list"}
153	if opt.SortKey != "" {
154		args = append(args, "--sort="+opt.SortKey)
155	} else {
156		args = append(args, "--sort=-creatordate")
157	}
158	args = append(args, "--end-of-options")
159	if opt.Pattern != "" {
160		args = append(args, opt.Pattern)
161	}
162
163	stdout, err := exec(ctx, r.path, args, opt.Envs)
164	if err != nil {
165		return nil, err
166	}
167
168	tags := strings.Split(string(stdout), "\n")
169	tags = tags[:len(tags)-1]
170
171	return tags, nil
172}
173
174// CreateTagOptions contains optional arguments for creating a tag.
175//
176// Docs: https://git-scm.com/docs/git-tag
177type CreateTagOptions struct {
178	// Annotated marks a tag as annotated rather than lightweight.
179	Annotated bool
180	// Message specifies a tagging message for the annotated tag. It is ignored when tag is not annotated.
181	Message string
182	// Author is the author of the tag. It is ignored when tag is not annotated.
183	Author *Signature
184	CommandOptions
185}
186
187// CreateTag creates a new tag on given revision.
188func (r *Repository) CreateTag(ctx context.Context, name, rev string, opts ...CreateTagOptions) error {
189	var opt CreateTagOptions
190	if len(opts) > 0 {
191		opt = opts[0]
192	}
193
194	args := []string{"tag"}
195	var envs []string
196	if opt.Annotated {
197		args = append(args, "-a", name)
198		args = append(args, "--message", opt.Message)
199		if opt.Author != nil {
200			envs = committerEnvs(opt.Author)
201		}
202		args = append(args, "--end-of-options")
203	} else {
204		args = append(args, "--end-of-options", name)
205	}
206	args = append(args, rev)
207	envs = append(envs, opt.Envs...)
208	_, err := exec(ctx, r.path, args, envs)
209	return err
210}
211
212// DeleteTagOptions contains optional arguments for deleting a tag.
213//
214// Docs: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---delete
215type DeleteTagOptions struct {
216	CommandOptions
217}
218
219// DeleteTag deletes a tag from the repository.
220func (r *Repository) DeleteTag(ctx context.Context, name string, opts ...DeleteTagOptions) error {
221	var opt DeleteTagOptions
222	if len(opts) > 0 {
223		opt = opts[0]
224	}
225
226	args := []string{"tag", "--delete", "--end-of-options", name}
227	_, err := exec(ctx, r.path, args, opt.Envs)
228	return err
229}