Merge pull request #558 from orirawlings/cloneAnnotatedTag

repository: Resolve commit when cloning annotated tag, fixes #557
diff --git a/repository.go b/repository.go
index 8110cf1..932b8d4 100644
--- a/repository.go
+++ b/repository.go
@@ -30,6 +30,7 @@
 	ErrRemoteExists            = errors.New("remote already exists	")
 	ErrWorktreeNotProvided     = errors.New("worktree should be provided")
 	ErrIsBareRepository        = errors.New("worktree not available in a bare repository")
+	ErrUnableToResolveCommit   = errors.New("unable to resolve commit")
 )
 
 // Repository represents a git repository
@@ -400,6 +401,25 @@
 	return r.Storer.SetConfig(cfg)
 }
 
+func (r *Repository) resolveToCommitHash(h plumbing.Hash) (plumbing.Hash, error) {
+	obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h)
+	if err != nil {
+		return plumbing.ZeroHash, err
+	}
+	switch obj.Type() {
+	case plumbing.TagObject:
+		t, err := object.DecodeTag(r.Storer, obj)
+		if err != nil {
+			return plumbing.ZeroHash, err
+		}
+		return r.resolveToCommitHash(t.Target)
+	case plumbing.CommitObject:
+		return h, nil
+	default:
+		return plumbing.ZeroHash, ErrUnableToResolveCommit
+	}
+}
+
 // Clone clones a remote repository
 func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
 	if err := o.Validate(); err != nil {
@@ -415,7 +435,7 @@
 		return err
 	}
 
-	head, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{
+	ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{
 		RefSpecs: r.cloneRefSpec(o, c),
 		Depth:    o.Depth,
 		Auth:     o.Auth,
@@ -431,6 +451,11 @@
 			return err
 		}
 
+		head, err := r.Head()
+		if err != nil {
+			return err
+		}
+
 		if err := w.Reset(&ResetOptions{Commit: head.Hash()}); err != nil {
 			return err
 		}
@@ -445,7 +470,7 @@
 		}
 	}
 
-	return r.updateRemoteConfigIfNeeded(o, c, head)
+	return r.updateRemoteConfigIfNeeded(o, c, ref)
 }
 
 const (
@@ -520,12 +545,12 @@
 		return nil, err
 	}
 
-	head, err := storer.ResolveReference(remoteRefs, ref)
+	resolvedRef, err := storer.ResolveReference(remoteRefs, ref)
 	if err != nil {
 		return nil, err
 	}
 
-	refsUpdated, err := r.updateReferences(remote.c.Fetch, head)
+	refsUpdated, err := r.updateReferences(remote.c.Fetch, resolvedRef)
 	if err != nil {
 		return nil, err
 	}
@@ -534,26 +559,30 @@
 		return nil, NoErrAlreadyUpToDate
 	}
 
-	return head, nil
+	return resolvedRef, nil
 }
 
 func (r *Repository) updateReferences(spec []config.RefSpec,
-	resolvedHead *plumbing.Reference) (updated bool, err error) {
+	resolvedRef *plumbing.Reference) (updated bool, err error) {
 
-	if !resolvedHead.Name().IsBranch() {
+	if !resolvedRef.Name().IsBranch() {
 		// Detached HEAD mode
-		head := plumbing.NewHashReference(plumbing.HEAD, resolvedHead.Hash())
+		h, err := r.resolveToCommitHash(resolvedRef.Hash())
+		if err != nil {
+			return false, err
+		}
+		head := plumbing.NewHashReference(plumbing.HEAD, h)
 		return updateReferenceStorerIfNeeded(r.Storer, head)
 	}
 
 	refs := []*plumbing.Reference{
-		// Create local reference for the resolved head
-		resolvedHead,
+		// Create local reference for the resolved ref
+		resolvedRef,
 		// Create local symbolic HEAD
-		plumbing.NewSymbolicReference(plumbing.HEAD, resolvedHead.Name()),
+		plumbing.NewSymbolicReference(plumbing.HEAD, resolvedRef.Name()),
 	}
 
-	refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedHead)...)
+	refs = append(refs, r.calculateRemoteHeadReference(spec, resolvedRef)...)
 
 	for _, ref := range refs {
 		u, err := updateReferenceStorerIfNeeded(r.Storer, ref)
diff --git a/repository_test.go b/repository_test.go
index 3da11f6..e944251 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -651,6 +651,27 @@
 	c.Assert(count, Equals, 15)
 }
 
+func (s *RepositorySuite) TestCloneDetachedHEADAnnotatedTag(c *C) {
+	r, _ := Init(memory.NewStorage(), nil)
+	err := r.clone(context.Background(), &CloneOptions{
+		URL:           s.GetLocalRepositoryURL(fixtures.ByTag("tags").One()),
+		ReferenceName: plumbing.ReferenceName("refs/tags/annotated-tag"),
+	})
+	c.Assert(err, IsNil)
+
+	head, err := r.Reference(plumbing.HEAD, false)
+	c.Assert(err, IsNil)
+	c.Assert(head, NotNil)
+	c.Assert(head.Type(), Equals, plumbing.HashReference)
+	c.Assert(head.Hash().String(), Equals, "f7b877701fbf855b44c0a9e86f3fdce2c298b07f")
+
+	count := 0
+	objects, err := r.Objects()
+	c.Assert(err, IsNil)
+	objects.ForEach(func(object.Object) error { count++; return nil })
+	c.Assert(count, Equals, 7)
+}
+
 func (s *RepositorySuite) TestPush(c *C) {
 	url := c.MkDir()
 	server, err := PlainInit(url, true)