1package git23import (4 "context"5 "errors"6 "strings"7)89const (10 RefsHeads = "refs/heads/"11 RefsTags = "refs/tags/"12)1314// RefShortName returns short name of heads or tags. Other references will15// 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 }2223 return ref24}2526// Reference contains information of a Git reference.27type Reference struct {28 ID string29 Refspec string30}3132// ShowRefVerifyOptions contains optional arguments for verifying a reference.33//34// Docs: https://git-scm.com/docs/git-show-ref#Documentation/git-show-ref.txt---verify35type ShowRefVerifyOptions struct {36 CommandOptions37}3839var ErrReferenceNotExist = errors.New("reference does not exist")4041// 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 ShowRefVerifyOptions45 if len(opts) > 0 {46 opt = opts[0]47 }4849 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 "", ErrReferenceNotExist54 }55 return "", err56 }57 return strings.Split(string(stdout), " ")[0], nil58}5960// BranchCommitID returns the commit ID of given branch if it exists in the61// 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}6566// TagCommitID returns the commit ID of given tag if it exists in the67// 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}7172// HasReference returns true if given reference exists in the repository. The73// 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 == nil77}7879// HasBranch returns true if given branch exists in the repository. The branch80// 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}8485// HasTag returns true if given tag exists in the repository. The tag must be86// 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}9091// 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 string95 // The name of the reference, e.g. "refs/heads/master". When set, it will be96 // used to update the symbolic ref.97 Ref string98 CommandOptions99}100101// SymbolicRef returns the reference name (e.g. "refs/heads/master") pointed by102// the symbolic ref. It returns an empty string and nil error when doing set103// operation.104func (r *Repository) SymbolicRef(ctx context.Context, opts ...SymbolicRefOptions) (string, error) {105 var opt SymbolicRefOptions106 if len(opts) > 0 {107 opt = opts[0]108 }109110 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 }118119 stdout, err := exec(ctx, r.path, args, opt.Envs)120 if err != nil {121 return "", err122 }123 return strings.TrimSpace(string(stdout)), nil124}125126// ShowRefOptions contains optional arguments for listing references.127//128// Docs: https://git-scm.com/docs/git-show-ref129type ShowRefOptions struct {130 // Indicates whether to include heads.131 Heads bool132 // Indicates whether to include tags.133 Tags bool134 // The list of patterns to filter results.135 Patterns []string136 CommandOptions137}138139// ShowRef returns a list of references in the repository.140func (r *Repository) ShowRef(ctx context.Context, opts ...ShowRefOptions) ([]*Reference, error) {141 var opt ShowRefOptions142 if len(opts) > 0 {143 opt = opts[0]144 }145146 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 }157158 stdout, err := exec(ctx, r.path, args, opt.Envs)159 if err != nil {160 return nil, err161 }162163 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 continue169 }170 refs = append(refs, &Reference{171 ID: fields[0],172 Refspec: fields[1],173 })174 }175 return refs, nil176}177178// 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, err183 }184185 branches := make([]string, len(heads))186 for i := range heads {187 branches[i] = strings.TrimPrefix(heads[i].Refspec, RefsHeads)188 }189 return branches, nil190}191192// DeleteBranchOptions contains optional arguments for deleting a branch.193//194// Docs: https://git-scm.com/docs/git-branch195type DeleteBranchOptions struct {196 // Indicates whether to force delete the branch.197 Force bool198 CommandOptions199}200201// DeleteBranch deletes the branch from the repository.202func (r *Repository) DeleteBranch(ctx context.Context, name string, opts ...DeleteBranchOptions) error {203 var opt DeleteBranchOptions204 if len(opts) > 0 {205 opt = opts[0]206 }207208 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 err217}