Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 38060c9

Browse files
authored
Merge pull request #928 from vancluever/f-add-tagging-support
git: Add tagging support
2 parents a2d62f5 + 9ce4eea commit 38060c9

File tree

6 files changed

+662
-69
lines changed

6 files changed

+662
-69
lines changed

object_walker.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ func (p *objectWalker) walkObjectTree(hash plumbing.Hash) error {
9494
return err
9595
}
9696
}
97+
case *object.Tag:
98+
return p.walkObjectTree(obj.Target)
9799
default:
98100
// Error out on unhandled object types.
99101
return fmt.Errorf("Unknown object %X %s %T\n", obj.ID(), obj.Type(), obj)

options.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package git
33
import (
44
"errors"
55
"regexp"
6+
"strings"
67

78
"golang.org/x/crypto/openpgp"
89
"gopkg.in/src-d/go-git.v4/config"
@@ -348,8 +349,9 @@ type CommitOptions struct {
348349
// Parents are the parents commits for the new commit, by default when
349350
// len(Parents) is zero, the hash of HEAD reference is used.
350351
Parents []plumbing.Hash
351-
// A key to sign the commit with. A nil value here means the commit will not
352-
// be signed. The private key must be present and already decrypted.
352+
// SignKey denotes a key to sign the commit with. A nil value here means the
353+
// commit will not be signed. The private key must be present and already
354+
// decrypted.
353355
SignKey *openpgp.Entity
354356
}
355357

@@ -377,6 +379,41 @@ func (o *CommitOptions) Validate(r *Repository) error {
377379
return nil
378380
}
379381

382+
var (
383+
ErrMissingName = errors.New("name field is required")
384+
ErrMissingTagger = errors.New("tagger field is required")
385+
ErrMissingMessage = errors.New("message field is required")
386+
)
387+
388+
// CreateTagOptions describes how a tag object should be created.
389+
type CreateTagOptions struct {
390+
// Tagger defines the signature of the tag creator.
391+
Tagger *object.Signature
392+
// Message defines the annotation of the tag. It is canonicalized during
393+
// validation into the format expected by git - no leading whitespace and
394+
// ending in a newline.
395+
Message string
396+
// SignKey denotes a key to sign the tag with. A nil value here means the tag
397+
// will not be signed. The private key must be present and already decrypted.
398+
SignKey *openpgp.Entity
399+
}
400+
401+
// Validate validates the fields and sets the default values.
402+
func (o *CreateTagOptions) Validate(r *Repository, hash plumbing.Hash) error {
403+
if o.Tagger == nil {
404+
return ErrMissingTagger
405+
}
406+
407+
if o.Message == "" {
408+
return ErrMissingMessage
409+
}
410+
411+
// Canonicalize the message into the expected message format.
412+
o.Message = strings.TrimSpace(o.Message) + "\n"
413+
414+
return nil
415+
}
416+
380417
// ListOptions describes how a remote list should be performed.
381418
type ListOptions struct {
382419
// Auth credentials, if required, to use with the remote repository.

plumbing/object/tag.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,14 @@ func (t *Tag) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
195195
return err
196196
}
197197

198-
if t.PGPSignature != "" && includeSig {
199-
// Split all the signature lines and write with a newline at the end.
200-
lines := strings.Split(t.PGPSignature, "\n")
201-
for _, line := range lines {
202-
if _, err = fmt.Fprintf(w, "%s\n", line); err != nil {
203-
return err
204-
}
198+
// Note that this is highly sensitive to what it sent along in the message.
199+
// Message *always* needs to end with a newline, or else the message and the
200+
// signature will be concatenated into a corrupt object. Since this is a
201+
// lower-level method, we assume you know what you are doing and have already
202+
// done the needful on the message in the caller.
203+
if includeSig {
204+
if _, err = fmt.Fprint(w, t.PGPSignature); err != nil {
205+
return err
205206
}
206207
}
207208

repository.go

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package git
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
78
stdioutil "io/ioutil"
89
"os"
10+
"path"
911
"path/filepath"
1012
"strings"
1113
"time"
1214

15+
"golang.org/x/crypto/openpgp"
1316
"gopkg.in/src-d/go-git.v4/config"
1417
"gopkg.in/src-d/go-git.v4/internal/revision"
1518
"gopkg.in/src-d/go-git.v4/plumbing"
@@ -32,7 +35,12 @@ var (
3235
// ErrBranchExists an error stating the specified branch already exists
3336
ErrBranchExists = errors.New("branch already exists")
3437
// ErrBranchNotFound an error stating the specified branch does not exist
35-
ErrBranchNotFound = errors.New("branch not found")
38+
ErrBranchNotFound = errors.New("branch not found")
39+
// ErrTagExists an error stating the specified tag already exists
40+
ErrTagExists = errors.New("tag already exists")
41+
// ErrTagNotFound an error stating the specified tag does not exist
42+
ErrTagNotFound = errors.New("tag not found")
43+
3644
ErrInvalidReference = errors.New("invalid reference, should be a tag or a branch")
3745
ErrRepositoryNotExists = errors.New("repository does not exist")
3846
ErrRepositoryAlreadyExists = errors.New("repository already exists")
@@ -478,6 +486,139 @@ func (r *Repository) DeleteBranch(name string) error {
478486
return r.Storer.SetConfig(cfg)
479487
}
480488

489+
// CreateTag creates a tag. If opts is included, the tag is an annotated tag,
490+
// otherwise a lightweight tag is created.
491+
func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error) {
492+
rname := plumbing.ReferenceName(path.Join("refs", "tags", name))
493+
494+
_, err := r.Storer.Reference(rname)
495+
switch err {
496+
case nil:
497+
// Tag exists, this is an error
498+
return nil, ErrTagExists
499+
case plumbing.ErrReferenceNotFound:
500+
// Tag missing, available for creation, pass this
501+
default:
502+
// Some other error
503+
return nil, err
504+
}
505+
506+
var target plumbing.Hash
507+
if opts != nil {
508+
target, err = r.createTagObject(name, hash, opts)
509+
if err != nil {
510+
return nil, err
511+
}
512+
} else {
513+
target = hash
514+
}
515+
516+
ref := plumbing.NewHashReference(rname, target)
517+
if err = r.Storer.SetReference(ref); err != nil {
518+
return nil, err
519+
}
520+
521+
return ref, nil
522+
}
523+
524+
func (r *Repository) createTagObject(name string, hash plumbing.Hash, opts *CreateTagOptions) (plumbing.Hash, error) {
525+
if err := opts.Validate(r, hash); err != nil {
526+
return plumbing.ZeroHash, err
527+
}
528+
529+
rawobj, err := object.GetObject(r.Storer, hash)
530+
if err != nil {
531+
return plumbing.ZeroHash, err
532+
}
533+
534+
tag := &object.Tag{
535+
Name: name,
536+
Tagger: *opts.Tagger,
537+
Message: opts.Message,
538+
TargetType: rawobj.Type(),
539+
Target: hash,
540+
}
541+
542+
if opts.SignKey != nil {
543+
sig, err := r.buildTagSignature(tag, opts.SignKey)
544+
if err != nil {
545+
return plumbing.ZeroHash, err
546+
}
547+
548+
tag.PGPSignature = sig
549+
}
550+
551+
obj := r.Storer.NewEncodedObject()
552+
if err := tag.Encode(obj); err != nil {
553+
return plumbing.ZeroHash, err
554+
}
555+
556+
return r.Storer.SetEncodedObject(obj)
557+
}
558+
559+
func (r *Repository) buildTagSignature(tag *object.Tag, signKey *openpgp.Entity) (string, error) {
560+
encoded := &plumbing.MemoryObject{}
561+
if err := tag.Encode(encoded); err != nil {
562+
return "", err
563+
}
564+
565+
rdr, err := encoded.Reader()
566+
if err != nil {
567+
return "", err
568+
}
569+
570+
var b bytes.Buffer
571+
if err := openpgp.ArmoredDetachSign(&b, signKey, rdr, nil); err != nil {
572+
return "", err
573+
}
574+
575+
return b.String(), nil
576+
}
577+
578+
// Tag returns a tag from the repository.
579+
//
580+
// If you want to check to see if the tag is an annotated tag, you can call
581+
// TagObject on the hash of the reference in ForEach:
582+
//
583+
// ref, err := r.Tag("v0.1.0")
584+
// if err != nil {
585+
// // Handle error
586+
// }
587+
//
588+
// obj, err := r.TagObject(ref.Hash())
589+
// switch err {
590+
// case nil:
591+
// // Tag object present
592+
// case plumbing.ErrObjectNotFound:
593+
// // Not a tag object
594+
// default:
595+
// // Some other error
596+
// }
597+
//
598+
func (r *Repository) Tag(name string) (*plumbing.Reference, error) {
599+
ref, err := r.Reference(plumbing.ReferenceName(path.Join("refs", "tags", name)), false)
600+
if err != nil {
601+
if err == plumbing.ErrReferenceNotFound {
602+
// Return a friendly error for this one, versus just ReferenceNotFound.
603+
return nil, ErrTagNotFound
604+
}
605+
606+
return nil, err
607+
}
608+
609+
return ref, nil
610+
}
611+
612+
// DeleteTag deletes a tag from the repository.
613+
func (r *Repository) DeleteTag(name string) error {
614+
_, err := r.Tag(name)
615+
if err != nil {
616+
return err
617+
}
618+
619+
return r.Storer.RemoveReference(plumbing.ReferenceName(path.Join("refs", "tags", name)))
620+
}
621+
481622
func (r *Repository) resolveToCommitHash(h plumbing.Hash) (plumbing.Hash, error) {
482623
obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h)
483624
if err != nil {
@@ -839,9 +980,31 @@ func (r *Repository) Log(o *LogOptions) (object.CommitIter, error) {
839980
return nil, fmt.Errorf("invalid Order=%v", o.Order)
840981
}
841982

842-
// Tags returns all the References from Tags. This method returns only lightweight
843-
// tags. Note that not all the tags are lightweight ones. To return annotated tags
844-
// too, you need to call TagObjects() method.
983+
// Tags returns all the tag References in a repository.
984+
//
985+
// If you want to check to see if the tag is an annotated tag, you can call
986+
// TagObject on the hash Reference passed in through ForEach:
987+
//
988+
// iter, err := r.Tags()
989+
// if err != nil {
990+
// // Handle error
991+
// }
992+
//
993+
// if err := iter.ForEach(func (ref *plumbing.Reference) error {
994+
// obj, err := r.TagObject(ref.Hash())
995+
// switch err {
996+
// case nil:
997+
// // Tag object present
998+
// case plumbing.ErrObjectNotFound:
999+
// // Not a tag object
1000+
// default:
1001+
// // Some other error
1002+
// return err
1003+
// }
1004+
// }); err != nil {
1005+
// // Handle outer iterator error
1006+
// }
1007+
//
8451008
func (r *Repository) Tags() (storer.ReferenceIter, error) {
8461009
refIter, err := r.Storer.IterReferences()
8471010
if err != nil {

0 commit comments

Comments
 (0)