Merge pull request #237 from libgit2/object-type

Move from an Object interface to a type
diff --git a/blob.go b/blob.go
index b1fc78a..16ec183 100644
--- a/blob.go
+++ b/blob.go
@@ -18,7 +18,7 @@
 )
 
 type Blob struct {
-	gitObject
+	Object
 	cast_ptr *C.git_blob
 }
 
diff --git a/commit.go b/commit.go
index 52f7c01..6830da3 100644
--- a/commit.go
+++ b/commit.go
@@ -14,7 +14,7 @@
 
 // Commit
 type Commit struct {
-	gitObject
+	Object
 	cast_ptr *C.git_commit
 }
 
@@ -37,7 +37,7 @@
 		return nil, MakeGitError(err)
 	}
 
-	return allocObject((*C.git_object)(ptr), c.repo).(*Tree), nil
+	return allocTree(ptr, c.repo), nil
 }
 
 func (c Commit) TreeId() *Oid {
@@ -61,7 +61,7 @@
 		return nil
 	}
 
-	return allocObject((*C.git_object)(cobj), c.repo).(*Commit)
+	return allocCommit(cobj, c.repo)
 }
 
 func (c *Commit) ParentId(n uint) *Oid {
diff --git a/describe.go b/describe.go
index c6f9a79..d75dbcb 100644
--- a/describe.go
+++ b/describe.go
@@ -127,7 +127,7 @@
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	ecode := C.git_describe_commit(&resultPtr, c.gitObject.ptr, cDescribeOpts)
+	ecode := C.git_describe_commit(&resultPtr, c.ptr, cDescribeOpts)
 	if ecode < 0 {
 		return nil, MakeGitError(ecode)
 	}
diff --git a/index_test.go b/index_test.go
index 7c65f4f..5f6b375 100644
--- a/index_test.go
+++ b/index_test.go
@@ -32,10 +32,11 @@
 	ref, err := repo.Head()
 	checkFatal(t, err)
 
-	obj, err := ref.Peel(ObjectTree);
+	obj, err := ref.Peel(ObjectTree)
 	checkFatal(t, err)
 
-	tree := obj.(*Tree)
+	tree, err := obj.AsTree()
+	checkFatal(t, err)
 
 	idx, err := NewIndex()
 	checkFatal(t, err)
diff --git a/object.go b/object.go
index 6ecebf8..1981980 100644
--- a/object.go
+++ b/object.go
@@ -4,7 +4,11 @@
 #include <git2.h>
 */
 import "C"
-import "runtime"
+import (
+	"errors"
+	"fmt"
+	"runtime"
+)
 
 type ObjectType int
 
@@ -17,15 +21,7 @@
 	ObjectTag    ObjectType = C.GIT_OBJ_TAG
 )
 
-type Object interface {
-	Free()
-	Id() *Oid
-	Type() ObjectType
-	Owner() *Repository
-	Peel(t ObjectType) (Object, error)
-}
-
-type gitObject struct {
+type Object struct {
 	ptr  *C.git_object
 	repo *Repository
 }
@@ -49,23 +45,128 @@
 	return ""
 }
 
-func (o gitObject) Id() *Oid {
+func (o *Object) Id() *Oid {
 	return newOidFromC(C.git_object_id(o.ptr))
 }
 
-func (o gitObject) Type() ObjectType {
+func (o *Object) Type() ObjectType {
 	return ObjectType(C.git_object_type(o.ptr))
 }
 
 // Owner returns a weak reference to the repository which owns this
-// object
-func (o gitObject) Owner() *Repository {
+// object. This won't keep the underlying repository alive.
+func (o *Object) Owner() *Repository {
 	return &Repository{
 		ptr: C.git_object_owner(o.ptr),
 	}
 }
 
-func (o *gitObject) Free() {
+func dupObject(obj *Object, kind ObjectType) (*C.git_object, error) {
+	if obj.Type() != kind {
+		return nil, errors.New(fmt.Sprintf("object is not a %v", kind))
+	}
+
+	var cobj *C.git_object
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C.git_object_dup(&cobj, obj.ptr); err < 0 {
+		return nil, MakeGitError(err)
+	}
+
+	return cobj, nil
+}
+
+func allocTree(ptr *C.git_tree, repo *Repository) *Tree {
+	tree := &Tree{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(tree, (*Tree).Free)
+
+	return tree
+}
+
+func (o *Object) AsTree() (*Tree, error) {
+	cobj, err := dupObject(o, ObjectTree)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocTree((*C.git_tree)(cobj), o.repo), nil
+}
+
+func allocCommit(ptr *C.git_commit, repo *Repository) *Commit {
+	commit := &Commit{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(commit, (*Commit).Free)
+
+	return commit
+}
+
+func (o *Object) AsCommit() (*Commit, error) {
+	cobj, err := dupObject(o, ObjectCommit)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocCommit((*C.git_commit)(cobj), o.repo), nil
+}
+
+func allocBlob(ptr *C.git_blob, repo *Repository) *Blob {
+	blob := &Blob{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(blob, (*Blob).Free)
+
+	return blob
+}
+
+func (o *Object) AsBlob() (*Blob, error) {
+	cobj, err := dupObject(o, ObjectBlob)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocBlob((*C.git_blob)(cobj), o.repo), nil
+}
+
+func allocTag(ptr *C.git_tag, repo *Repository) *Tag {
+	tag := &Tag{
+		Object: Object{
+			ptr:  (*C.git_object)(ptr),
+			repo: repo,
+		},
+		cast_ptr: ptr,
+	}
+	runtime.SetFinalizer(tag, (*Tag).Free)
+
+	return tag
+}
+
+func (o *Object) AsTag() (*Tag, error) {
+	cobj, err := dupObject(o, ObjectTag)
+	if err != nil {
+		return nil, err
+	}
+
+	return allocTag((*C.git_tag)(cobj), o.repo), nil
+}
+
+func (o *Object) Free() {
 	runtime.SetFinalizer(o, nil)
 	C.git_object_free(o.ptr)
 }
@@ -82,7 +183,7 @@
 //
 // If peeling a tag we discover an object which cannot be peeled to the target
 // type due to the object model, an error will be returned.
-func (o *gitObject) Peel(t ObjectType) (Object, error) {
+func (o *Object) Peel(t ObjectType) (*Object, error) {
 	var cobj *C.git_object
 
 	runtime.LockOSThread()
@@ -95,44 +196,12 @@
 	return allocObject(cobj, o.repo), nil
 }
 
-func allocObject(cobj *C.git_object, repo *Repository) Object {
-	obj := gitObject{
+func allocObject(cobj *C.git_object, repo *Repository) *Object {
+	obj := &Object{
 		ptr:  cobj,
 		repo: repo,
 	}
+	runtime.SetFinalizer(obj, (*Object).Free)
 
-	switch ObjectType(C.git_object_type(cobj)) {
-	case ObjectCommit:
-		commit := &Commit{
-			gitObject: obj,
-			cast_ptr:  (*C.git_commit)(cobj),
-		}
-		runtime.SetFinalizer(commit, (*Commit).Free)
-		return commit
-
-	case ObjectTree:
-		tree := &Tree{
-			gitObject: obj,
-			cast_ptr:  (*C.git_tree)(cobj),
-		}
-		runtime.SetFinalizer(tree, (*Tree).Free)
-		return tree
-
-	case ObjectBlob:
-		blob := &Blob{
-			gitObject: obj,
-			cast_ptr:  (*C.git_blob)(cobj),
-		}
-		runtime.SetFinalizer(blob, (*Blob).Free)
-		return blob
-	case ObjectTag:
-		tag := &Tag{
-			gitObject: obj,
-			cast_ptr:  (*C.git_tag)(cobj),
-		}
-		runtime.SetFinalizer(tag, (*Tag).Free)
-		return tag
-	}
-
-	return nil
+	return obj
 }
diff --git a/object_test.go b/object_test.go
index ef6c5a1..2ae2a6a 100644
--- a/object_test.go
+++ b/object_test.go
@@ -10,12 +10,12 @@
 
 	commitId, treeId := seedTestRepo(t, repo)
 
-	var obj Object
+	var obj *Object
 
 	commit, err := repo.LookupCommit(commitId)
 	checkFatal(t, err)
 
-	obj = commit
+	obj = &commit.Object
 	if obj.Type() != ObjectCommit {
 		t.Fatalf("Wrong object type, expected commit, have %v", obj.Type())
 	}
@@ -27,13 +27,13 @@
 	tree, err := repo.LookupTree(treeId)
 	checkFatal(t, err)
 
-	obj = tree
+	obj = &tree.Object
 	if obj.Type() != ObjectTree {
 		t.Fatalf("Wrong object type, expected tree, have %v", obj.Type())
 	}
 
-	tree2, ok := obj.(*Tree)
-	if !ok {
+	tree2, err := obj.AsTree()
+	if err != nil {
 		t.Fatalf("Converting back to *Tree is not ok")
 	}
 
@@ -46,16 +46,16 @@
 		t.Fatal("Wrong filemode for \"README\"")
 	}
 
-	_, ok = obj.(*Commit)
-	if ok {
+	_, err = obj.AsCommit()
+	if err == nil {
 		t.Fatalf("*Tree is somehow the same as *Commit")
 	}
 
 	obj, err = repo.Lookup(tree.Id())
 	checkFatal(t, err)
 
-	_, ok = obj.(*Tree)
-	if !ok {
+	_, err = obj.AsTree()
+	if err != nil {
 		t.Fatalf("Lookup creates the wrong type")
 	}
 
@@ -99,8 +99,8 @@
 	tree, err := repo.LookupTree(treeId)
 	checkFatal(t, err)
 
-	checkOwner(t, repo, commit)
-	checkOwner(t, repo, tree)
+	checkOwner(t, repo, commit.Object)
+	checkOwner(t, repo, tree.Object)
 }
 
 func TestObjectPeel(t *testing.T) {
@@ -109,7 +109,7 @@
 
 	commitID, treeID := seedTestRepo(t, repo)
 
-	var obj Object
+	var obj *Object
 
 	commit, err := repo.LookupCommit(commitID)
 	checkFatal(t, err)
diff --git a/reference.go b/reference.go
index 140082f..463f2fc 100644
--- a/reference.go
+++ b/reference.go
@@ -263,7 +263,7 @@
 	return nil
 }
 
-func (v *Reference) Peel(t ObjectType) (Object, error) {
+func (v *Reference) Peel(t ObjectType) (*Object, error) {
 	var cobj *C.git_object
 
 	runtime.LockOSThread()
diff --git a/repository.go b/repository.go
index 62fde6d..2e05780 100644
--- a/repository.go
+++ b/repository.go
@@ -145,7 +145,7 @@
 	return newIndexFromC(ptr), nil
 }
 
-func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) {
+func (v *Repository) lookupType(id *Oid, t ObjectType) (*Object, error) {
 	var ptr *C.git_object
 
 	runtime.LockOSThread()
@@ -159,7 +159,7 @@
 	return allocObject(ptr, v), nil
 }
 
-func (v *Repository) Lookup(id *Oid) (Object, error) {
+func (v *Repository) Lookup(id *Oid) (*Object, error) {
 	return v.lookupType(id, ObjectAny)
 }
 
@@ -169,7 +169,7 @@
 		return nil, err
 	}
 
-	return obj.(*Tree), nil
+	return obj.AsTree()
 }
 
 func (v *Repository) LookupCommit(id *Oid) (*Commit, error) {
@@ -178,7 +178,7 @@
 		return nil, err
 	}
 
-	return obj.(*Commit), nil
+	return obj.AsCommit()
 }
 
 func (v *Repository) LookupBlob(id *Oid) (*Blob, error) {
@@ -187,7 +187,7 @@
 		return nil, err
 	}
 
-	return obj.(*Blob), nil
+	return obj.AsBlob()
 }
 
 func (v *Repository) LookupTag(id *Oid) (*Tag, error) {
@@ -196,7 +196,7 @@
 		return nil, err
 	}
 
-	return obj.(*Tag), nil
+	return obj.AsTag()
 }
 
 func (v *Repository) Head() (*Reference, error) {
diff --git a/reset.go b/reset.go
index b5b7435..9da7625 100644
--- a/reset.go
+++ b/reset.go
@@ -17,7 +17,7 @@
 func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error {
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
-	ret := C.git_reset(r.ptr, commit.gitObject.ptr, C.git_reset_t(resetType), opts.toC())
+	ret := C.git_reset(r.ptr, commit.ptr, C.git_reset_t(resetType), opts.toC())
 
 	if ret < 0 {
 		return MakeGitError(ret)
diff --git a/revparse.go b/revparse.go
index 7eb04f1..950932b 100644
--- a/revparse.go
+++ b/revparse.go
@@ -20,16 +20,16 @@
 )
 
 type Revspec struct {
-	to    Object
-	from  Object
+	to    *Object
+	from  *Object
 	flags RevparseFlag
 }
 
-func (rs *Revspec) To() Object {
+func (rs *Revspec) To() *Object {
 	return rs.to
 }
 
-func (rs *Revspec) From() Object {
+func (rs *Revspec) From() *Object {
 	return rs.from
 }
 
@@ -38,8 +38,8 @@
 }
 
 func newRevspecFromC(ptr *C.git_revspec, repo *Repository) *Revspec {
-	var to Object
-	var from Object
+	var to *Object
+	var from *Object
 
 	if ptr.to != nil {
 		to = allocObject(ptr.to, repo)
@@ -73,7 +73,7 @@
 	return newRevspecFromC(&crevspec, r), nil
 }
 
-func (v *Repository) RevparseSingle(spec string) (Object, error) {
+func (v *Repository) RevparseSingle(spec string) (*Object, error) {
 	cspec := C.CString(spec)
 	defer C.free(unsafe.Pointer(cspec))
 
@@ -90,7 +90,7 @@
 	return allocObject(ptr, v), nil
 }
 
-func (r *Repository) RevparseExt(spec string) (Object, *Reference, error) {
+func (r *Repository) RevparseExt(spec string) (*Object, *Reference, error) {
 	cspec := C.CString(spec)
 	defer C.free(unsafe.Pointer(cspec))
 
diff --git a/revparse_test.go b/revparse_test.go
index 091a76b..75e9ffd 100644
--- a/revparse_test.go
+++ b/revparse_test.go
@@ -46,7 +46,7 @@
 	}
 }
 
-func checkObject(t *testing.T, obj Object, id *Oid) {
+func checkObject(t *testing.T, obj *Object, id *Oid) {
 	if obj == nil {
 		t.Fatalf("bad object")
 	}
diff --git a/tag.go b/tag.go
index ca85156..8957430 100644
--- a/tag.go
+++ b/tag.go
@@ -13,7 +13,7 @@
 
 // Tag
 type Tag struct {
-	gitObject
+	Object
 	cast_ptr *C.git_tag
 }
 
@@ -30,7 +30,7 @@
 	return newSignatureFromC(cast_ptr)
 }
 
-func (t Tag) Target() Object {
+func (t Tag) Target() *Object {
 	var ptr *C.git_object
 	ret := C.git_tag_target(&ptr, t.cast_ptr)
 
@@ -70,7 +70,7 @@
 	}
 	defer C.git_signature_free(taggerSig)
 
-	ctarget := commit.gitObject.ptr
+	ctarget := commit.ptr
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
@@ -102,7 +102,7 @@
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
-	ctarget := commit.gitObject.ptr
+	ctarget := commit.ptr
 
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
diff --git a/tree.go b/tree.go
index f543c11..8288176 100644
--- a/tree.go
+++ b/tree.go
@@ -23,7 +23,7 @@
 )
 
 type Tree struct {
-	gitObject
+	Object
 	cast_ptr *C.git_tree
 }