git-module

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

  1package git
  2
  3import (
  4	"context"
  5	"errors"
  6	"strings"
  7)
  8
  9const (
 10	RefsHeads = "refs/heads/"
 11	RefsTags  = "refs/tags/"
 12)
 13
 14// RefShortName returns short name of heads or tags. Other references will
 15// return original string.
 16func RefShortName(ref string) string {
 17	if strings.HasPrefix(ref, RefsHeads) {
 18		return ref[len(RefsHeads):]
 19	} else if strings.HasPrefix(ref, RefsTags) {
 20		return ref[len(RefsTags):]
 21	}
 22
 23	return ref
 24}
 25
 26// Reference contains information of a Git reference.
 27type Reference struct {
 28	ID      string
 29	Refspec string
 30}
 31
 32// ShowRefVerifyOptions contains optional arguments for verifying a reference.
 33//
 34// Docs: https://git-scm.com/docs/git-show-ref#Documentation/git-show-ref.txt---verify
 35type ShowRefVerifyOptions struct {
 36	CommandOptions
 37}
 38
 39var ErrReferenceNotExist = errors.New("reference does not exist")
 40
 41// ShowRefVerify returns the commit ID of given reference (e.g.
 42// "refs/heads/master") if it exists in the repository.
 43func (r *Repository) ShowRefVerify(ctx context.Context, ref string, opts ...ShowRefVerifyOptions) (string, error) {
 44	var opt ShowRefVerifyOptions
 45	if len(opts) > 0 {
 46		opt = opts[0]
 47	}
 48
 49	args := []string{"show-ref", "--verify", "--end-of-options", ref}
 50	stdout, err := exec(ctx, r.path, args, opt.Envs)
 51	if err != nil {
 52		if strings.Contains(err.Error(), "not a valid ref") {
 53			return "", ErrReferenceNotExist
 54		}
 55		return "", err
 56	}
 57	return strings.Split(string(stdout), " ")[0], nil
 58}
 59
 60// BranchCommitID returns the commit ID of given branch if it exists in the
 61// repository. The branch must be given in short name e.g. "master".
 62func (r *Repository) BranchCommitID(ctx context.Context, branch string, opts ...ShowRefVerifyOptions) (string, error) {
 63	return r.ShowRefVerify(ctx, RefsHeads+branch, opts...)
 64}
 65
 66// TagCommitID returns the commit ID of given tag if it exists in the
 67// repository. The tag must be given in short name e.g. "v1.0.0".
 68func (r *Repository) TagCommitID(ctx context.Context, tag string, opts ...ShowRefVerifyOptions) (string, error) {
 69	return r.ShowRefVerify(ctx, RefsTags+tag, opts...)
 70}
 71
 72// HasReference returns true if given reference exists in the repository. The
 73// reference must be given in full refspec, e.g. "refs/heads/master".
 74func (r *Repository) HasReference(ctx context.Context, ref string, opts ...ShowRefVerifyOptions) bool {
 75	_, err := r.ShowRefVerify(ctx, ref, opts...)
 76	return err == nil
 77}
 78
 79// HasBranch returns true if given branch exists in the repository. The branch
 80// must be given in short name e.g. "master".
 81func (r *Repository) HasBranch(ctx context.Context, branch string, opts ...ShowRefVerifyOptions) bool {
 82	return r.HasReference(ctx, RefsHeads+branch, opts...)
 83}
 84
 85// HasTag returns true if given tag exists in the repository. The tag must be
 86// given in short name e.g. "v1.0.0".
 87func (r *Repository) HasTag(ctx context.Context, tag string, opts ...ShowRefVerifyOptions) bool {
 88	return r.HasReference(ctx, RefsTags+tag, opts...)
 89}
 90
 91// SymbolicRefOptions contains optional arguments for get and set symbolic ref.
 92type SymbolicRefOptions struct {
 93	// The name of the symbolic ref. When not set, default ref "HEAD" is used.
 94	Name string
 95	// The name of the reference, e.g. "refs/heads/master". When set, it will be
 96	// used to update the symbolic ref.
 97	Ref string
 98	CommandOptions
 99}
100
101// SymbolicRef returns the reference name (e.g. "refs/heads/master") pointed by
102// the symbolic ref. It returns an empty string and nil error when doing set
103// operation.
104func (r *Repository) SymbolicRef(ctx context.Context, opts ...SymbolicRefOptions) (string, error) {
105	var opt SymbolicRefOptions
106	if len(opts) > 0 {
107		opt = opts[0]
108	}
109
110	args := []string{"symbolic-ref"}
111	if opt.Name == "" {
112		opt.Name = "HEAD"
113	}
114	args = append(args, "--end-of-options", opt.Name)
115	if opt.Ref != "" {
116		args = append(args, opt.Ref)
117	}
118
119	stdout, err := exec(ctx, r.path, args, opt.Envs)
120	if err != nil {
121		return "", err
122	}
123	return strings.TrimSpace(string(stdout)), nil
124}
125
126// ShowRefOptions contains optional arguments for listing references.
127//
128// Docs: https://git-scm.com/docs/git-show-ref
129type ShowRefOptions struct {
130	// Indicates whether to include heads.
131	Heads bool
132	// Indicates whether to include tags.
133	Tags bool
134	// The list of patterns to filter results.
135	Patterns []string
136	CommandOptions
137}
138
139// ShowRef returns a list of references in the repository.
140func (r *Repository) ShowRef(ctx context.Context, opts ...ShowRefOptions) ([]*Reference, error) {
141	var opt ShowRefOptions
142	if len(opts) > 0 {
143		opt = opts[0]
144	}
145
146	args := []string{"show-ref"}
147	if opt.Heads {
148		args = append(args, "--heads")
149	}
150	if opt.Tags {
151		args = append(args, "--tags")
152	}
153	args = append(args, "--")
154	if len(opt.Patterns) > 0 {
155		args = append(args, opt.Patterns...)
156	}
157
158	stdout, err := exec(ctx, r.path, args, opt.Envs)
159	if err != nil {
160		return nil, err
161	}
162
163	lines := strings.Split(string(stdout), "\n")
164	refs := make([]*Reference, 0, len(lines))
165	for i := range lines {
166		fields := strings.Fields(lines[i])
167		if len(fields) != 2 {
168			continue
169		}
170		refs = append(refs, &Reference{
171			ID:      fields[0],
172			Refspec: fields[1],
173		})
174	}
175	return refs, nil
176}
177
178// Branches returns a list of all branches in the repository.
179func (r *Repository) Branches(ctx context.Context) ([]string, error) {
180	heads, err := r.ShowRef(ctx, ShowRefOptions{Heads: true})
181	if err != nil {
182		return nil, err
183	}
184
185	branches := make([]string, len(heads))
186	for i := range heads {
187		branches[i] = strings.TrimPrefix(heads[i].Refspec, RefsHeads)
188	}
189	return branches, nil
190}
191
192// DeleteBranchOptions contains optional arguments for deleting a branch.
193//
194// Docs: https://git-scm.com/docs/git-branch
195type DeleteBranchOptions struct {
196	// Indicates whether to force delete the branch.
197	Force bool
198	CommandOptions
199}
200
201// DeleteBranch deletes the branch from the repository.
202func (r *Repository) DeleteBranch(ctx context.Context, name string, opts ...DeleteBranchOptions) error {
203	var opt DeleteBranchOptions
204	if len(opts) > 0 {
205		opt = opts[0]
206	}
207
208	args := []string{"branch"}
209	if opt.Force {
210		args = append(args, "-D")
211	} else {
212		args = append(args, "-d")
213	}
214	args = append(args, "--end-of-options", name)
215	_, err := exec(ctx, r.path, args, opt.Envs)
216	return err
217}