Merge pull request #792 from ajnavarro/fix/support-no-symref-capability

Resolve HEAD if symRefs capability is not supported
diff --git a/plumbing/protocol/packp/advrefs.go b/plumbing/protocol/packp/advrefs.go
index 7d644bc..684e76a 100644
--- a/plumbing/protocol/packp/advrefs.go
+++ b/plumbing/protocol/packp/advrefs.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"sort"
 	"strings"
 
 	"gopkg.in/src-d/go-git.v4/plumbing"
@@ -68,30 +69,119 @@
 
 func (a *AdvRefs) AllReferences() (memory.ReferenceStorage, error) {
 	s := memory.ReferenceStorage{}
-	if err := addRefs(s, a); err != nil {
+	if err := a.addRefs(s); err != nil {
 		return s, plumbing.NewUnexpectedError(err)
 	}
 
 	return s, nil
 }
 
-func addRefs(s storer.ReferenceStorer, ar *AdvRefs) error {
-	for name, hash := range ar.References {
+func (a *AdvRefs) addRefs(s storer.ReferenceStorer) error {
+	for name, hash := range a.References {
 		ref := plumbing.NewReferenceFromStrings(name, hash.String())
 		if err := s.SetReference(ref); err != nil {
 			return err
 		}
 	}
 
-	return addSymbolicRefs(s, ar)
+	if a.supportSymrefs() {
+		return a.addSymbolicRefs(s)
+	}
+
+	return a.resolveHead(s)
 }
 
-func addSymbolicRefs(s storer.ReferenceStorer, ar *AdvRefs) error {
-	if !hasSymrefs(ar) {
+// If the server does not support symrefs capability,
+// we need to guess the reference where HEAD is pointing to.
+//
+// Git versions prior to 1.8.4.3 has an special procedure to get
+// the reference where is pointing to HEAD:
+// - Check if a reference called master exists. If exists and it
+//	 has the same hash as HEAD hash, we can say that HEAD is pointing to master
+// - If master does not exists or does not have the same hash as HEAD,
+//   order references and check in that order if that reference has the same
+//   hash than HEAD. If yes, set HEAD pointing to that branch hash
+// - If no reference is found, throw an error
+func (a *AdvRefs) resolveHead(s storer.ReferenceStorer) error {
+	if a.Head == nil {
 		return nil
 	}
 
-	for _, symref := range ar.Capabilities.Get(capability.SymRef) {
+	ref, err := s.Reference(plumbing.ReferenceName(plumbing.Master))
+
+	// check first if HEAD is pointing to master
+	if err == nil {
+		ok, err := a.createHeadIfCorrectReference(ref, s)
+		if err != nil {
+			return err
+		}
+
+		if ok {
+			return nil
+		}
+	}
+
+	if err != nil && err != plumbing.ErrReferenceNotFound {
+		return err
+	}
+
+	// From here we are trying to guess the branch that HEAD is pointing
+	refIter, err := s.IterReferences()
+	if err != nil {
+		return err
+	}
+
+	var refNames []string
+	err = refIter.ForEach(func(r *plumbing.Reference) error {
+		refNames = append(refNames, string(r.Name()))
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	sort.Strings(refNames)
+
+	var headSet bool
+	for _, refName := range refNames {
+		ref, err := s.Reference(plumbing.ReferenceName(refName))
+		if err != nil {
+			return err
+		}
+		ok, err := a.createHeadIfCorrectReference(ref, s)
+		if err != nil {
+			return err
+		}
+		if ok {
+			headSet = true
+			break
+		}
+	}
+
+	if !headSet {
+		return plumbing.ErrReferenceNotFound
+	}
+
+	return nil
+}
+
+func (a *AdvRefs) createHeadIfCorrectReference(
+	reference *plumbing.Reference,
+	s storer.ReferenceStorer) (bool, error) {
+	if reference.Hash() == *a.Head {
+		headRef := plumbing.NewSymbolicReference(plumbing.HEAD, reference.Name())
+		if err := s.SetReference(headRef); err != nil {
+			return false, err
+		}
+
+		return true, nil
+	}
+
+	return false, nil
+}
+
+func (a *AdvRefs) addSymbolicRefs(s storer.ReferenceStorer) error {
+	for _, symref := range a.Capabilities.Get(capability.SymRef) {
 		chunks := strings.Split(symref, ":")
 		if len(chunks) != 2 {
 			err := fmt.Errorf("bad number of `:` in symref value (%q)", symref)
@@ -108,6 +198,6 @@
 	return nil
 }
 
-func hasSymrefs(ar *AdvRefs) bool {
-	return ar.Capabilities.Supports(capability.SymRef)
+func (a *AdvRefs) supportSymrefs() bool {
+	return a.Capabilities.Supports(capability.SymRef)
 }
diff --git a/plumbing/protocol/packp/advrefs_test.go b/plumbing/protocol/packp/advrefs_test.go
index 0180fd3..bb8d032 100644
--- a/plumbing/protocol/packp/advrefs_test.go
+++ b/plumbing/protocol/packp/advrefs_test.go
@@ -79,6 +79,79 @@
 	c.Assert(err, NotNil)
 }
 
+func (s *AdvRefSuite) TestNoSymRefCapabilityHeadToMaster(c *C) {
+	a := NewAdvRefs()
+	headHash := plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c")
+	a.Head = &headHash
+	ref := plumbing.NewHashReference(plumbing.Master, plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"))
+
+	err := a.AddReference(ref)
+	c.Assert(err, IsNil)
+
+	storage, err := a.AllReferences()
+	c.Assert(err, IsNil)
+
+	head, err := storage.Reference(plumbing.HEAD)
+	c.Assert(err, IsNil)
+	c.Assert(head.Target(), Equals, ref.Name())
+}
+
+func (s *AdvRefSuite) TestNoSymRefCapabilityHeadToOtherThanMaster(c *C) {
+	a := NewAdvRefs()
+	headHash := plumbing.NewHash("0000000000000000000000000000000000000000")
+	a.Head = &headHash
+	ref1 := plumbing.NewHashReference(plumbing.Master, plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"))
+	ref2 := plumbing.NewHashReference("other/ref", plumbing.NewHash("0000000000000000000000000000000000000000"))
+
+	err := a.AddReference(ref1)
+	c.Assert(err, IsNil)
+	err = a.AddReference(ref2)
+	c.Assert(err, IsNil)
+
+	storage, err := a.AllReferences()
+	c.Assert(err, IsNil)
+
+	head, err := storage.Reference(plumbing.HEAD)
+	c.Assert(err, IsNil)
+	c.Assert(head.Hash(), Equals, ref2.Hash())
+}
+
+func (s *AdvRefSuite) TestNoSymRefCapabilityHeadToNoRef(c *C) {
+	a := NewAdvRefs()
+	headHash := plumbing.NewHash("0000000000000000000000000000000000000000")
+	a.Head = &headHash
+	ref := plumbing.NewHashReference(plumbing.Master, plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"))
+
+	err := a.AddReference(ref)
+	c.Assert(err, IsNil)
+
+	_, err = a.AllReferences()
+	c.Assert(err, NotNil)
+}
+
+func (s *AdvRefSuite) TestNoSymRefCapabilityHeadToNoMasterAlphabeticallyOrdered(c *C) {
+	a := NewAdvRefs()
+	headHash := plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c")
+	a.Head = &headHash
+	ref1 := plumbing.NewHashReference(plumbing.Master, plumbing.NewHash("0000000000000000000000000000000000000000"))
+	ref2 := plumbing.NewHashReference("aaaaaaaaaaaaaaa", plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"))
+	ref3 := plumbing.NewHashReference("bbbbbbbbbbbbbbb", plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"))
+
+	err := a.AddReference(ref1)
+	c.Assert(err, IsNil)
+	err = a.AddReference(ref3)
+	c.Assert(err, IsNil)
+	err = a.AddReference(ref2)
+	c.Assert(err, IsNil)
+
+	storage, err := a.AllReferences()
+	c.Assert(err, IsNil)
+
+	head, err := storage.Reference(plumbing.HEAD)
+	c.Assert(err, IsNil)
+	c.Assert(head.Target(), Equals, ref2.Name())
+}
+
 type AdvRefsDecodeEncodeSuite struct{}
 
 var _ = Suite(&AdvRefsDecodeEncodeSuite{})