diff --git a/blob.go b/blob.go index 73e3ab3..cc26c92 100644 --- a/blob.go +++ b/blob.go @@ -7,24 +7,18 @@ package git */ import "C" import ( - "runtime" "unsafe" ) type Blob struct { - ptr *C.git_object + gitObject } -func (v *Blob) Free() { - runtime.SetFinalizer(v, nil) - C.git_object_free(v.ptr) -} - -func (v *Blob) Size() int64 { +func (v Blob) Size() int64 { return int64(C.git_blob_rawsize(v.ptr)) } -func (v *Blob) Contents() []byte { +func (v Blob) Contents() []byte { size := C.int(C.git_blob_rawsize(v.ptr)) buffer := unsafe.Pointer(C.git_blob_rawcontent(v.ptr)) return C.GoBytes(buffer, size) diff --git a/commit.go b/commit.go index d31f684..9730a70 100644 --- a/commit.go +++ b/commit.go @@ -9,46 +9,40 @@ extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr); import "C" import ( - "runtime" "unsafe" "time" ) // Commit type Commit struct { - ptr *C.git_commit + gitObject } -func (c *Commit) Id() *Oid { - return newOidFromC(C.git_commit_id(c.ptr)) -} - -func (c *Commit) Message() string { +func (c Commit) Message() string { return C.GoString(C.git_commit_message(c.ptr)) } -func (c *Commit) Tree() (*Tree, error) { - tree := new(Tree) +func (c Commit) Tree() (*Tree, error) { + var ptr *C.git_object - err := C.git_commit_tree(&tree.ptr, c.ptr) + err := C.git_commit_tree(&ptr, c.ptr) if err < 0 { return nil, LastError() } - runtime.SetFinalizer(tree, (*Tree).Free) - return tree, nil + return allocObject(ptr).(*Tree), nil } -func (c *Commit) TreeId() *Oid { +func (c Commit) TreeId() *Oid { return newOidFromC(C.git_commit_tree_id(c.ptr)) } -func (c *Commit) Author() *Signature { +func (c Commit) Author() *Signature { ptr := C.git_commit_author(c.ptr) return newSignatureFromC(ptr) } -func (c *Commit) Committer() *Signature { +func (c Commit) Committer() *Signature { ptr := C.git_commit_committer(c.ptr) return newSignatureFromC(ptr) } diff --git a/git_test.go b/git_test.go new file mode 100644 index 0000000..52aea1d --- /dev/null +++ b/git_test.go @@ -0,0 +1,47 @@ +package git + +import ( + "testing" + "io/ioutil" + "time" +) + +func createTestRepo(t *testing.T) *Repository { + // figure out where we can create the test repo + path, err := ioutil.TempDir("", "git2go") + checkFatal(t, err) + repo, err := InitRepository(path, false) + checkFatal(t, err) + + tmpfile := "README" + err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) + checkFatal(t, err) + + return repo +} + +func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { + loc, err := time.LoadLocation("Europe/Berlin") + checkFatal(t, err) + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "random@hacker.com", + When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), + } + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath("README") + checkFatal(t, err) + treeId, err := idx.WriteTree() + checkFatal(t, err) + + message := "This is a commit\n" + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) + checkFatal(t, err) + + return commitId, treeId +} + diff --git a/index_test.go b/index_test.go index fe6fb87..9828d0f 100644 --- a/index_test.go +++ b/index_test.go @@ -4,23 +4,8 @@ import ( "os" "runtime" "testing" - "io/ioutil" ) -func createTestRepo(t *testing.T) *Repository { - // figure out where we can create the test repo - path, err := ioutil.TempDir("", "git2go") - checkFatal(t, err) - repo, err := InitRepository(path, false) - checkFatal(t, err) - - tmpfile := "README" - err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) - checkFatal(t, err) - - return repo -} - func TestCreateRepoAndStage(t *testing.T) { repo := createTestRepo(t) defer os.RemoveAll(repo.Workdir()) diff --git a/object.go b/object.go new file mode 100644 index 0000000..c6cd8a8 --- /dev/null +++ b/object.go @@ -0,0 +1,84 @@ +package git + +/* +#cgo pkg-config: libgit2 +#include +#include +*/ +import "C" +import "runtime" + +type ObjectType int + +var ( + OBJ_ANY ObjectType = C.GIT_OBJ_ANY + OBJ_BAD ObjectType = C.GIT_OBJ_BAD + OBJ_COMMIT ObjectType = C.GIT_OBJ_COMMIT + OBJ_TREE ObjectType = C.GIT_OBJ_TREE + OBJ_BLOB ObjectType = C.GIT_OBJ_BLOB + OBJ_TAG ObjectType = C.GIT_OBJ_TAG +) + +type Object interface { + Free() + Id() *Oid + Type() ObjectType +} + +type gitObject struct { + ptr *C.git_object +} + +func (t ObjectType) String() (string) { + switch (t) { + case OBJ_ANY: + return "Any" + case OBJ_BAD: + return "Bad" + case OBJ_COMMIT: + return "Commit" + case OBJ_TREE: + return "Tree" + case OBJ_BLOB: + return "Blob" + case OBJ_TAG: + return "tag" + } + // Never reached + return "" +} + +func (o gitObject) Id() *Oid { + return newOidFromC(C.git_commit_id(o.ptr)) +} + +func (o gitObject) Type() ObjectType { + return ObjectType(C.git_object_type(o.ptr)) +} + +func (o *gitObject) Free() { + runtime.SetFinalizer(o, nil) + C.git_commit_free(o.ptr) +} + +func allocObject(cobj *C.git_object) Object { + + switch ObjectType(C.git_object_type(cobj)) { + case OBJ_COMMIT: + commit := &Commit{gitObject{cobj}} + runtime.SetFinalizer(commit, (*Commit).Free) + return commit + + case OBJ_TREE: + tree := &Tree{gitObject{cobj}} + runtime.SetFinalizer(tree, (*Tree).Free) + return tree + + case OBJ_BLOB: + blob := &Blob{gitObject{cobj}} + runtime.SetFinalizer(blob, (*Blob).Free) + return blob + } + + return nil +} diff --git a/object_test.go b/object_test.go new file mode 100644 index 0000000..82dce98 --- /dev/null +++ b/object_test.go @@ -0,0 +1,68 @@ +package git + +import ( + "os" + "testing" +) + +func TestObjectPoymorphism(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + commitId, treeId := seedTestRepo(t, repo) + + var obj Object + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + obj = commit + if obj.Type() != OBJ_COMMIT { + t.Fatalf("Wrong object type, expected commit, have %v", obj.Type()) + } + + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + + obj = tree + if obj.Type() != OBJ_TREE { + t.Fatalf("Wrong object type, expected tree, have %v", obj.Type()) + } + + tree2, ok := obj.(*Tree) + if !ok { + t.Fatalf("Converting back to *Tree is not ok") + } + + if tree2.EntryByName("README") == nil { + t.Fatalf("Tree did not have expected \"README\" entry") + } + + _, ok = obj.(*Commit) + if ok { + t.Fatalf("*Tree is somehow the same as *Commit") + } + + obj, err = repo.Lookup(tree.Id()) + checkFatal(t, err) + + _, ok = obj.(*Tree) + if !ok { + t.Fatalf("Lookup creates the wrong type") + } + + if obj.Type() != OBJ_TREE { + t.Fatalf("Type() doesn't agree with dynamic type") + } + + obj, err = repo.RevparseSingle("HEAD") + checkFatal(t, err) + if obj.Type() != OBJ_COMMIT || obj.Id().String() != commit.Id().String() { + t.Fatalf("Failed to parse the right revision") + } + + obj, err = repo.RevparseSingle("HEAD^{tree}") + checkFatal(t, err) + if obj.Type() != OBJ_TREE || obj.Id().String() != tree.Id().String() { + t.Fatalf("Failed to parse the right revision") + } +} diff --git a/odb.go b/odb.go index bf17171..37d9fcd 100644 --- a/odb.go +++ b/odb.go @@ -12,15 +12,6 @@ import ( "runtime" ) -var ( - OBJ_ANY = C.GIT_OBJ_ANY - OBJ_BAD = C.GIT_OBJ_BAD - OBJ_COMMIT = C.GIT_OBJ_COMMIT - OBJ_TREE = C.GIT_OBJ_TREE - OBJ_BLOB = C.GIT_OBJ_BLOB - OBJ_TAG = C.GIT_OBJ_TAG -) - type Odb struct { ptr *C.git_odb } diff --git a/reference_test.go b/reference_test.go index 8043833..3ea421d 100644 --- a/reference_test.go +++ b/reference_test.go @@ -4,35 +4,15 @@ import ( "os" "runtime" "testing" - "time" ) func TestRefModification(t *testing.T) { repo := createTestRepo(t) defer os.RemoveAll(repo.Workdir()) - loc, err := time.LoadLocation("Europe/Berlin") - checkFatal(t, err) - sig := &Signature{ - Name: "Rand Om Hacker", - Email: "random@hacker.com", - When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), - } + commitId, treeId := seedTestRepo(t, repo) - idx, err := repo.Index() - checkFatal(t, err) - err = idx.AddByPath("README") - checkFatal(t, err) - treeId, err := idx.WriteTree() - checkFatal(t, err) - - message := "This is a commit\n" - tree, err := repo.LookupTree(treeId) - checkFatal(t, err) - commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) - checkFatal(t, err) - - _, err = repo.CreateReference("refs/tags/tree", treeId, true) + _, err := repo.CreateReference("refs/tags/tree", treeId, true) checkFatal(t, err) tag, err := repo.LookupReference("refs/tags/tree") diff --git a/repository.go b/repository.go index 3de4974..0a07dc3 100644 --- a/repository.go +++ b/repository.go @@ -72,35 +72,45 @@ func (v *Repository) Index() (*Index, error) { return newIndexFromC(ptr), nil } -func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { - tree := new(Tree) - ret := C.git_tree_lookup(&tree.ptr, v.ptr, oid.toC()) +func (v *Repository) lookupType(oid *Oid, t ObjectType) (Object, error) { + var ptr *C.git_object + ret := C.git_object_lookup(&ptr, v.ptr, oid.toC(), C.git_otype(t)) if ret < 0 { return nil, LastError() } - return tree, nil + return allocObject(ptr), nil } -func (v *Repository) LookupCommit(o *Oid) (*Commit, error) { - commit := new(Commit) - ecode := C.git_commit_lookup(&commit.ptr, v.ptr, o.toC()) - if ecode < 0 { - return nil, LastError() - } - - return commit, nil +func (v *Repository) Lookup(oid *Oid) (Object, error) { + return v.lookupType(oid, OBJ_ANY) } -func (v *Repository) LookupBlob(o *Oid) (*Blob, error) { - blob := new(Blob) - ecode := C.git_blob_lookup(&blob.ptr, v.ptr, o.toC()) - if ecode < 0 { - return nil, LastError() +func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { + obj, err := v.lookupType(oid, OBJ_TREE) + if err != nil { + return nil, err } - runtime.SetFinalizer(blob, (*Blob).Free) - return blob, nil + return obj.(*Tree), nil +} + +func (v *Repository) LookupCommit(oid *Oid) (*Commit, error) { + obj, err := v.lookupType(oid, OBJ_COMMIT) + if err != nil { + return nil, err + } + + return obj.(*Commit), nil +} + +func (v *Repository) LookupBlob(oid *Oid) (*Blob, error) { + obj, err := v.lookupType(oid, OBJ_BLOB) + if err != nil { + return nil, err + } + + return obj.(*Blob), nil } func (v *Repository) LookupReference(name string) (*Reference, error) { @@ -241,3 +251,15 @@ func (v *Repository) TreeBuilder() (*TreeBuilder, error) { return bld, nil } +func (v *Repository) RevparseSingle(spec string) (Object, error) { + cspec := C.CString(spec) + defer C.free(unsafe.Pointer(cspec)) + + var ptr *C.git_object + ecode := C.git_revparse_single(&ptr, v.ptr, cspec) + if ecode < 0 { + return nil, LastError() + } + + return allocObject(ptr), nil +} diff --git a/tree.go b/tree.go index dc82929..9a9fd63 100644 --- a/tree.go +++ b/tree.go @@ -14,40 +14,24 @@ import ( ) type Tree struct { - ptr *C.git_tree + gitObject } type TreeEntry struct { Name string Id *Oid - Type int + Type ObjectType } func newTreeEntry(entry *C.git_tree_entry) *TreeEntry { return &TreeEntry{ C.GoString(C.git_tree_entry_name(entry)), newOidFromC(C.git_tree_entry_id(entry)), - int(C.git_tree_entry_type(entry)), + ObjectType(C.git_tree_entry_type(entry)), } } -func (t *Tree) Free() { - runtime.SetFinalizer(t, nil) - C.git_tree_free(t.ptr) -} - -func TreeLookup(repo *Repository, oid *Oid) (*Tree, error) { - tree := new(Tree) - err := C.git_tree_lookup(&tree.ptr, repo.ptr, oid.toC()) - if err < 0 { - return nil, LastError() - } - - runtime.SetFinalizer(tree, (*Tree).Free) - return tree, nil -} - -func (t *Tree) EntryByName(filename string) *TreeEntry { +func (t Tree) EntryByName(filename string) *TreeEntry { cname := C.CString(filename) defer C.free(unsafe.Pointer(cname)) @@ -59,7 +43,7 @@ func (t *Tree) EntryByName(filename string) *TreeEntry { return newTreeEntry(entry) } -func (t *Tree) EntryByIndex(index uint64) *TreeEntry { +func (t Tree) EntryByIndex(index uint64) *TreeEntry { entry := C.git_tree_entry_byindex(t.ptr, C.size_t(index)) if entry == nil { return nil @@ -68,7 +52,7 @@ func (t *Tree) EntryByIndex(index uint64) *TreeEntry { return newTreeEntry(entry) } -func (t *Tree) EntryCount() uint64 { +func (t Tree) EntryCount() uint64 { num := C.git_tree_entrycount(t.ptr) return uint64(num) } @@ -84,7 +68,7 @@ func CallbackGitTreeWalk(_root unsafe.Pointer, _entry unsafe.Pointer, ptr unsafe return C.int(callback(root, newTreeEntry(entry))) } -func (t *Tree) Walk(callback TreeWalkCallback) error { +func (t Tree) Walk(callback TreeWalkCallback) error { err := C._go_git_treewalk( t.ptr, C.GIT_TREEWALK_PRE,