diff --git a/.travis.yml b/.travis.yml index f84d07e..f8b7e93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,19 @@ language: go +install: + - cd "${HOME}" + - wget -O libgit2-0.22.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz + - tar -xzvf libgit2-0.22.1.tar.gz + - cd libgit2-0.22.1 && mkdir build && cd build + - cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_C_FLAGS=-fPIC -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && sudo make install + - sudo ldconfig + - cd "${TRAVIS_BUILD_DIR}" + go: - 1.1 - 1.2 - 1.3 + - 1.4 - tip matrix: diff --git a/blame_test.go b/blame_test.go index 1785042..a2a4d38 100644 --- a/blame_test.go +++ b/blame_test.go @@ -1,15 +1,13 @@ package git import ( - "os" "reflect" "testing" ) func TestBlame(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId1, _ := seedTestRepo(t, repo) commitId2, _ := updateReadme(t, repo, "foo\nbar\nbaz\n") diff --git a/blob.go b/blob.go index 5a33bd8..b1fc78a 100644 --- a/blob.go +++ b/blob.go @@ -60,8 +60,13 @@ type BlobCallbackData struct { } //export blobChunkCb -func blobChunkCb(buffer *C.char, maxLen C.size_t, payload unsafe.Pointer) int { - data := (*BlobCallbackData)(payload) +func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*BlobCallbackData) + if !ok { + panic("could not retrieve blob callback data") + } + goBuf, err := data.Callback(int(maxLen)) if err == io.EOF { return 0 @@ -83,8 +88,12 @@ func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunk defer C.free(unsafe.Pointer(chintPath)) } oid := C.git_oid{} + payload := &BlobCallbackData{Callback: callback} - ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, unsafe.Pointer(payload)) + handle := pointerHandles.Track(payload) + defer pointerHandles.Untrack(handle) + + ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle) if payload.Error != nil { return nil, payload.Error } diff --git a/blob_test.go b/blob_test.go index e075192..2b5ec4f 100644 --- a/blob_test.go +++ b/blob_test.go @@ -1,13 +1,12 @@ package git import ( - "os" "testing" ) func TestCreateBlobFromBuffer(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) id, err := repo.CreateBlobFromBuffer(make([]byte, 0)) checkFatal(t, err) diff --git a/branch_test.go b/branch_test.go index 09ebeba..a0834a8 100644 --- a/branch_test.go +++ b/branch_test.go @@ -1,9 +1,13 @@ package git -import "testing" +import ( + "testing" +) func TestBranchIterator(t *testing.T) { repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) i, err := repo.NewBranchIterator(BranchLocal) @@ -24,6 +28,8 @@ func TestBranchIterator(t *testing.T) { func TestBranchIteratorEach(t *testing.T) { repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) i, err := repo.NewBranchIterator(BranchLocal) diff --git a/checkout.go b/checkout.go index 98c1ee6..d747344 100644 --- a/checkout.go +++ b/checkout.go @@ -43,6 +43,7 @@ type CheckoutOpts struct { FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY TargetDirectory string // Alternative checkout path to workdir + Paths []string } func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts { @@ -83,6 +84,11 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi if opts.TargetDirectory != "" { ptr.target_directory = C.CString(opts.TargetDirectory) } + if len(opts.Paths) > 0 { + ptr.paths.strings = makeCStringsFromStrings(opts.Paths) + ptr.paths.count = C.size_t(len(opts.Paths)) + } + return ptr } @@ -91,6 +97,9 @@ func freeCheckoutOpts(ptr *C.git_checkout_options) { return } C.free(unsafe.Pointer(ptr.target_directory)) + if ptr.paths.count > 0 { + freeStrarray(&ptr.paths) + } } // Updates files in the index and the working tree to match the content of @@ -146,4 +155,4 @@ func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOpts) error { } return nil -} +} \ No newline at end of file diff --git a/cherrypick_test.go b/cherrypick_test.go index 141a55d..a3246bd 100644 --- a/cherrypick_test.go +++ b/cherrypick_test.go @@ -34,6 +34,8 @@ func readReadme(t *testing.T, repo *Repository) string { func TestCherrypick(t *testing.T) { repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + c1, _ := seedTestRepo(t, repo) c2, _ := updateReadme(t, repo, content) diff --git a/clone_test.go b/clone_test.go index 97366bf..fd83fec 100644 --- a/clone_test.go +++ b/clone_test.go @@ -2,22 +2,21 @@ package git import ( "io/ioutil" - "os" "testing" ) func TestClone(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) seedTestRepo(t, repo) path, err := ioutil.TempDir("", "git2go") checkFatal(t, err) - _, err = Clone(repo.Path(), path, &CloneOptions{Bare: true}) - defer os.RemoveAll(path) + repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true}) + defer cleanupTestRepo(t, repo2) checkFatal(t, err) } diff --git a/commit.go b/commit.go index 57e1a77..52f7c01 100644 --- a/commit.go +++ b/commit.go @@ -9,6 +9,7 @@ import "C" import ( "runtime" + "unsafe" ) // Commit @@ -70,3 +71,40 @@ func (c *Commit) ParentId(n uint) *Oid { func (c *Commit) ParentCount() uint { return uint(C.git_commit_parentcount(c.cast_ptr)) } + +func (c *Commit) Amend(refname string, author, committer *Signature, message string, tree *Tree) (*Oid, error) { + var cref *C.char + if refname == "" { + cref = nil + } else { + cref = C.CString(refname) + defer C.free(unsafe.Pointer(cref)) + } + + cmsg := C.CString(message) + defer C.free(unsafe.Pointer(cmsg)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + authorSig, err := author.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(committerSig) + + oid := new(Oid) + + cerr := C.git_commit_amend(oid.toC(), c.cast_ptr, cref, authorSig, committerSig, nil, cmsg, tree.cast_ptr) + if cerr < 0 { + return nil, MakeGitError(cerr) + } + + return oid, nil +} diff --git a/diff.go b/diff.go index 63fa867..5e03175 100644 --- a/diff.go +++ b/diff.go @@ -265,7 +265,11 @@ func (diff *Diff) ForEach(cbFile DiffForEachFileCallback, detail DiffDetail) err data := &diffForEachData{ FileCallback: cbFile, } - ecode := C._go_git_diff_foreach(diff.ptr, 1, intHunks, intLines, unsafe.Pointer(data)) + + handle := pointerHandles.Track(data) + defer pointerHandles.Untrack(handle) + + ecode := C._go_git_diff_foreach(diff.ptr, 1, intHunks, intLines, handle) if ecode < 0 { return data.Error } @@ -273,8 +277,12 @@ func (diff *Diff) ForEach(cbFile DiffForEachFileCallback, detail DiffDetail) err } //export diffForEachFileCb -func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe.Pointer) int { - data := (*diffForEachData)(payload) +func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } data.HunkCallback = nil if data.FileCallback != nil { @@ -292,8 +300,12 @@ func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe type DiffForEachHunkCallback func(DiffHunk) (DiffForEachLineCallback, error) //export diffForEachHunkCb -func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload unsafe.Pointer) int { - data := (*diffForEachData)(payload) +func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } data.LineCallback = nil if data.HunkCallback != nil { @@ -311,9 +323,12 @@ func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload u type DiffForEachLineCallback func(DiffLine) error //export diffForEachLineCb -func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, payload unsafe.Pointer) int { - - data := (*diffForEachData)(payload) +func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffForEachData) + if !ok { + panic("could not retrieve data for handle") + } err := data.LineCallback(diffLineFromC(delta, hunk, line)) if err != nil { @@ -479,9 +494,15 @@ type diffNotifyData struct { } //export diffNotifyCb -func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, matched_pathspec *C.char, payload unsafe.Pointer) int { +func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, matched_pathspec *C.char, handle unsafe.Pointer) int { diff_so_far := (*C.git_diff)(_diff_so_far) - data := (*diffNotifyData)(payload) + + payload := pointerHandles.Get(handle) + data, ok := payload.(*diffNotifyData) + if !ok { + panic("could not retrieve data for handle") + } + if data != nil { if data.Diff == nil { data.Diff = newDiffFromC(diff_so_far) @@ -507,6 +528,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d notifyData = &diffNotifyData{ Callback: opts.NotifyCallback, } + if opts.Pathspec != nil { cpathspec.count = C.size_t(len(opts.Pathspec)) cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) @@ -527,7 +549,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d if opts.NotifyCallback != nil { C._go_git_setup_diff_notify_callbacks(copts) - copts.notify_payload = unsafe.Pointer(notifyData) + copts.notify_payload = pointerHandles.Track(notifyData) } } return @@ -539,6 +561,9 @@ func freeDiffOptions(copts *C.git_diff_options) { freeStrarray(&cpathspec) C.free(unsafe.Pointer(copts.old_prefix)) C.free(unsafe.Pointer(copts.new_prefix)) + if copts.notify_payload != nil { + pointerHandles.Untrack(copts.notify_payload) + } } } @@ -595,3 +620,53 @@ func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff, } return newDiffFromC(diffPtr), nil } + +func (v *Repository) DiffTreeToWorkdirWithIndex(oldTree *Tree, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var oldPtr *C.git_tree + + if oldTree != nil { + oldPtr = oldTree.cast_ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_tree_to_workdir_with_index(&diffPtr, v.ptr, oldPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} + +func (v *Repository) DiffIndexToWorkdir(index *Index, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var indexPtr *C.git_index + + if index != nil { + indexPtr = index.ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_index_to_workdir(&diffPtr, v.ptr, indexPtr, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + if notifyData != nil && notifyData.Diff != nil { + return notifyData.Diff, nil + } + return newDiffFromC(diffPtr), nil +} diff --git a/diff_test.go b/diff_test.go index fc6fed9..464fae6 100644 --- a/diff_test.go +++ b/diff_test.go @@ -2,15 +2,13 @@ package git import ( "errors" - "os" "strings" "testing" ) func TestFindSimilar(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) originalTree, newTree := createTestTrees(t, repo) @@ -65,8 +63,7 @@ func TestFindSimilar(t *testing.T) { func TestDiffTreeToTree(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) originalTree, newTree := createTestTrees(t, repo) diff --git a/git.go b/git.go index 8e78710..5f69837 100644 --- a/git.go +++ b/git.go @@ -92,7 +92,11 @@ var ( ErrInvalid = errors.New("Invalid state for operation") ) +var pointerHandles *HandleList + func init() { + pointerHandles = NewHandleList() + C.git_libgit2_init() // This is not something we should be doing, as we may be diff --git a/git_test.go b/git_test.go index b9cf0a9..58caf71 100644 --- a/git_test.go +++ b/git_test.go @@ -2,11 +2,24 @@ package git import ( "io/ioutil" + "os" "path" "testing" "time" ) +func cleanupTestRepo(t *testing.T, r *Repository) { + var err error + if r.IsBare() { + err = os.RemoveAll(r.Path()) + } else { + err = os.RemoveAll(r.Workdir()) + } + checkFatal(t, err) + + r.Free() +} + func createTestRepo(t *testing.T) *Repository { // figure out where we can create the test repo path, err := ioutil.TempDir("", "git2go") diff --git a/handles.go b/handles.go new file mode 100644 index 0000000..ec62a48 --- /dev/null +++ b/handles.go @@ -0,0 +1,84 @@ +package git + +import ( + "fmt" + "sync" + "unsafe" +) + +type HandleList struct { + sync.RWMutex + // stores the Go pointers + handles []interface{} + // indicates which indices are in use + set map[int]bool +} + +func NewHandleList() *HandleList { + return &HandleList{ + handles: make([]interface{}, 5), + set: make(map[int]bool), + } +} + +// findUnusedSlot finds the smallest-index empty space in our +// list. You must only run this function while holding a write lock. +func (v *HandleList) findUnusedSlot() int { + for i := 1; i < len(v.handles); i++ { + isUsed := v.set[i] + if !isUsed { + return i + } + } + + // reaching here means we've run out of entries so append and + // return the new index, which is equal to the old length. + slot := len(v.handles) + v.handles = append(v.handles, nil) + + return slot +} + +// Track adds the given pointer to the list of pointers to track and +// returns a pointer value which can be passed to C as an opaque +// pointer. +func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { + v.Lock() + + slot := v.findUnusedSlot() + v.handles[slot] = pointer + v.set[slot] = true + + v.Unlock() + + return unsafe.Pointer(&slot) +} + +// Untrack stops tracking the pointer given by the handle +func (v *HandleList) Untrack(handle unsafe.Pointer) { + slot := *(*int)(handle) + + v.Lock() + + v.handles[slot] = nil + delete(v.set, slot) + + v.Unlock() +} + +// Get retrieves the pointer from the given handle +func (v *HandleList) Get(handle unsafe.Pointer) interface{} { + slot := *(*int)(handle) + + v.RLock() + + if _, ok := v.set[slot]; !ok { + panic(fmt.Sprintf("invalid pointer handle: %p", handle)) + } + + ptr := v.handles[slot] + + v.RUnlock() + + return ptr +} diff --git a/index.go b/index.go index 6b90758..c1bfb74 100644 --- a/index.go +++ b/index.go @@ -96,6 +96,30 @@ func NewIndex() (*Index, error) { return &Index{ptr: ptr}, nil } +// OpenIndex creates a new index at the given path. If the file does +// not exist it will be created when Write() is called. +func OpenIndex(path string) (*Index, error) { + var ptr *C.git_index + + var cpath = C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_index_open(&ptr, cpath); err < 0 { + return nil, MakeGitError(err) + } + + return &Index{ptr: ptr}, nil +} + +// Path returns the index' path on disk or an empty string if it +// exists only in memory. +func (v *Index) Path() string { + return C.GoString(C.git_index_path(v.ptr)) +} + // Add adds or replaces the given entry to the index, making a copy of // the data func (v *Index) Add(entry *IndexEntry) error { @@ -138,16 +162,17 @@ func (v *Index) AddAll(pathspecs []string, flags IndexAddOpts, callback IndexMat runtime.LockOSThread() defer runtime.UnlockOSThread() - var cb *IndexMatchedPathCallback + var handle unsafe.Pointer if callback != nil { - cb = &callback + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) } ret := C._go_git_index_add_all( v.ptr, &cpathspecs, C.uint(flags), - unsafe.Pointer(cb), + handle, ) if ret < 0 { return MakeGitError(ret) @@ -164,15 +189,16 @@ func (v *Index) UpdateAll(pathspecs []string, callback IndexMatchedPathCallback) runtime.LockOSThread() defer runtime.UnlockOSThread() - var cb *IndexMatchedPathCallback + var handle unsafe.Pointer if callback != nil { - cb = &callback + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) } ret := C._go_git_index_update_all( v.ptr, &cpathspecs, - unsafe.Pointer(cb), + handle, ) if ret < 0 { return MakeGitError(ret) @@ -189,15 +215,16 @@ func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback) runtime.LockOSThread() defer runtime.UnlockOSThread() - var cb *IndexMatchedPathCallback + var handle unsafe.Pointer if callback != nil { - cb = &callback + handle = pointerHandles.Track(callback) + defer pointerHandles.Untrack(handle) } ret := C._go_git_index_remove_all( v.ptr, &cpathspecs, - unsafe.Pointer(cb), + handle, ) if ret < 0 { return MakeGitError(ret) @@ -207,8 +234,11 @@ func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback) //export indexMatchedPathCallback func indexMatchedPathCallback(cPath, cMatchedPathspec *C.char, payload unsafe.Pointer) int { - callback := (*IndexMatchedPathCallback)(payload) - return (*callback)(C.GoString(cPath), C.GoString(cMatchedPathspec)) + if callback, ok := pointerHandles.Get(payload).(IndexMatchedPathCallback); ok { + return callback(C.GoString(cPath), C.GoString(cMatchedPathspec)) + } else { + panic("invalid matched path callback") + } } func (v *Index) RemoveByPath(path string) error { @@ -240,6 +270,20 @@ func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) { return oid, nil } +// ReadTree replaces the contents of the index with those of the given +// tree +func (v *Index) ReadTree(tree *Tree) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_read_tree(v.ptr, tree.cast_ptr); + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + func (v *Index) WriteTree() (*Oid, error) { oid := new(Oid) @@ -287,6 +331,7 @@ func (v *Index) HasConflicts() bool { return C.git_index_has_conflicts(v.ptr) != 0 } +// FIXME: this might return an error func (v *Index) CleanupConflicts() { C.git_index_conflict_cleanup(v.ptr) } diff --git a/index_test.go b/index_test.go index 98d9a31..7c65f4f 100644 --- a/index_test.go +++ b/index_test.go @@ -9,7 +9,7 @@ import ( func TestCreateRepoAndStage(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) idx, err := repo.Index() checkFatal(t, err) @@ -23,12 +23,40 @@ func TestCreateRepoAndStage(t *testing.T) { } } +func TestIndexReadTree(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + _, _ = seedTestRepo(t, repo) + + ref, err := repo.Head() + checkFatal(t, err) + + obj, err := ref.Peel(ObjectTree); + checkFatal(t, err) + + tree := obj.(*Tree) + + idx, err := NewIndex() + checkFatal(t, err) + + err = idx.ReadTree(tree) + checkFatal(t, err) + + id, err := idx.WriteTreeTo(repo) + checkFatal(t, err) + + if tree.Id().Cmp(id) != 0 { + t.Fatalf("Read and written trees are not the same") + } +} + func TestIndexWriteTreeTo(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) repo2 := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo2) idx, err := repo.Index() checkFatal(t, err) @@ -44,7 +72,7 @@ func TestIndexWriteTreeTo(t *testing.T) { func TestIndexAddAndWriteTreeTo(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) odb, err := repo.Odb() checkFatal(t, err) @@ -55,6 +83,10 @@ func TestIndexAddAndWriteTreeTo(t *testing.T) { idx, err := NewIndex() checkFatal(t, err) + if idx.Path() != "" { + t.Fatal("in-memory repo has a path") + } + entry := IndexEntry{ Path: "README", Id: blobID, @@ -74,7 +106,7 @@ func TestIndexAddAndWriteTreeTo(t *testing.T) { func TestIndexAddAllNoCallback(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) err := ioutil.WriteFile(repo.Workdir()+"/README", []byte("foo\n"), 0644) checkFatal(t, err) @@ -95,7 +127,7 @@ func TestIndexAddAllNoCallback(t *testing.T) { func TestIndexAddAllCallback(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) err := ioutil.WriteFile(repo.Workdir()+"/README", []byte("foo\n"), 0644) checkFatal(t, err) @@ -121,6 +153,33 @@ func TestIndexAddAllCallback(t *testing.T) { } } +func TestIndexOpen(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + path := repo.Workdir() + "/heyindex" + + _, err := os.Stat(path) + if !os.IsNotExist(err) { + t.Fatal("new index file already exists") + } + + idx, err := OpenIndex(path) + checkFatal(t, err) + + if path != idx.Path() { + t.Fatalf("mismatched index paths, expected %v, got %v", path, idx.Path()) + } + + err = idx.Write() + checkFatal(t, err) + + _, err = os.Stat(path) + if os.IsNotExist(err) { + t.Fatal("new index file did not get written") + } +} + func checkFatal(t *testing.T, err error) { if err == nil { return @@ -129,8 +188,7 @@ func checkFatal(t *testing.T, err error) { // The failure happens at wherever we were called, not here _, file, line, ok := runtime.Caller(1) if !ok { - t.Fatal() + t.Fatalf("Unable to get caller") } - t.Fatalf("Fail at %v:%v; %v", file, line, err) } diff --git a/merge_test.go b/merge_test.go index 1eba806..0b1faca 100644 --- a/merge_test.go +++ b/merge_test.go @@ -1,13 +1,13 @@ package git import ( - "os" "testing" ) func TestMergeWithSelf(t *testing.T) { - repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) master, err := repo.LookupReference("refs/heads/master") @@ -23,8 +23,9 @@ func TestMergeWithSelf(t *testing.T) { } func TestMergeAnalysisWithSelf(t *testing.T) { - repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) master, err := repo.LookupReference("refs/heads/master") @@ -44,7 +45,6 @@ func TestMergeAnalysisWithSelf(t *testing.T) { } func TestMergeSameFile(t *testing.T) { - file := MergeFileInput{ Path: "test", Mode: 33188, @@ -68,8 +68,7 @@ func TestMergeSameFile(t *testing.T) { } func TestMergeTreesWithoutAncestor(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) _, originalTreeId := seedTestRepo(t, repo) originalTree, err := repo.LookupTree(originalTreeId) diff --git a/note_test.go b/note_test.go index f5e9c01..e6c378d 100644 --- a/note_test.go +++ b/note_test.go @@ -2,7 +2,6 @@ package git import ( "fmt" - "os" "reflect" "testing" "time" @@ -10,7 +9,7 @@ import ( func TestCreateNote(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo) @@ -29,7 +28,8 @@ func TestCreateNote(t *testing.T) { func TestNoteIterator(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) notes := make([]*Note, 5) @@ -64,7 +64,7 @@ func TestNoteIterator(t *testing.T) { func TestRemoveNote(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo) @@ -87,7 +87,7 @@ func TestRemoveNote(t *testing.T) { func TestDefaultNoteRef(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) ref, err := repo.DefaultNoteRef() checkFatal(t, err) diff --git a/object_test.go b/object_test.go index f525351..aa295e5 100644 --- a/object_test.go +++ b/object_test.go @@ -1,13 +1,13 @@ package git import ( - "os" "testing" ) func TestObjectPoymorphism(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + commitId, treeId := seedTestRepo(t, repo) var obj Object @@ -89,7 +89,8 @@ func checkOwner(t *testing.T, repo *Repository, obj Object) { func TestObjectOwner(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + commitId, treeId := seedTestRepo(t, repo) commit, err := repo.LookupCommit(commitId) diff --git a/odb.go b/odb.go index ba03860..6b21329 100644 --- a/odb.go +++ b/odb.go @@ -98,8 +98,12 @@ type foreachData struct { } //export odbForEachCb -func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int { - data := (*foreachData)(payload) +func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int { + data, ok := pointerHandles.Get(handle).(*foreachData) + + if !ok { + panic("could not retrieve handle") + } err := data.callback(newOidFromC(id)) if err != nil { @@ -119,7 +123,10 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&data)) + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + ret := C._go_git_odb_foreach(v.ptr, handle) if ret == C.GIT_EUSER { return data.err } else if ret < 0 { diff --git a/odb_test.go b/odb_test.go index 5e6b7ff..2fb6840 100644 --- a/odb_test.go +++ b/odb_test.go @@ -3,13 +3,13 @@ package git import ( "errors" "io" - "os" "testing" ) func TestOdbStream(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + _, _ = seedTestRepo(t, repo) odb, error := repo.Odb() @@ -38,7 +38,8 @@ func TestOdbStream(t *testing.T) { func TestOdbHash(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + _, _ = seedTestRepo(t, repo) odb, error := repo.Odb() @@ -64,7 +65,8 @@ Initial commit.` func TestOdbForeach(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + _, _ = seedTestRepo(t, repo) odb, err := repo.Odb() @@ -79,7 +81,7 @@ func TestOdbForeach(t *testing.T) { checkFatal(t, err) if count != expect { - t.Fatalf("Expected %v objects, got %v") + t.Fatalf("Expected %v objects, got %v", expect, count) } expect = 1 diff --git a/packbuilder.go b/packbuilder.go index 54a8390..4dc352c 100644 --- a/packbuilder.go +++ b/packbuilder.go @@ -110,8 +110,13 @@ type packbuilderCbData struct { } //export packbuilderForEachCb -func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Pointer) int { - data := (*packbuilderCbData)(payload) +func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*packbuilderCbData) + if !ok { + panic("could not get packbuilder CB data") + } + slice := C.GoBytes(buf, C.int(size)) err := data.callback(slice) @@ -130,11 +135,13 @@ func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error { callback: callback, err: nil, } + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) runtime.LockOSThread() defer runtime.UnlockOSThread() - err := C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(&data)) + err := C._go_git_packbuilder_foreach(pb.ptr, handle) if err == C.GIT_EUSER { return data.err } diff --git a/patch_test.go b/patch_test.go index a061142..2d52fb4 100644 --- a/patch_test.go +++ b/patch_test.go @@ -7,8 +7,7 @@ import ( func TestPatch(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - //defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) _, originalTreeId := seedTestRepo(t, repo) originalTree, err := repo.LookupTree(originalTreeId) diff --git a/push_test.go b/push_test.go index ad72e4d..4686c65 100644 --- a/push_test.go +++ b/push_test.go @@ -1,15 +1,15 @@ package git import ( - "os" "testing" ) func TestRemotePush(t *testing.T) { repo := createBareTestRepo(t) - defer os.RemoveAll(repo.Path()) + defer cleanupTestRepo(t, repo) + localRepo := createTestRepo(t) - defer os.RemoveAll(localRepo.Workdir()) + defer cleanupTestRepo(t, localRepo) remote, err := localRepo.CreateRemote("test_push", repo.Path()) checkFatal(t, err) diff --git a/reference_test.go b/reference_test.go index 562e276..e891e7a 100644 --- a/reference_test.go +++ b/reference_test.go @@ -1,7 +1,6 @@ package git import ( - "os" "runtime" "sort" "testing" @@ -10,7 +9,7 @@ import ( func TestRefModification(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, treeId := seedTestRepo(t, repo) @@ -55,7 +54,7 @@ func TestRefModification(t *testing.T) { func TestReferenceIterator(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) loc, err := time.LoadLocation("Europe/Berlin") checkFatal(t, err) @@ -133,7 +132,8 @@ func TestReferenceIterator(t *testing.T) { func TestReferenceOwner(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + commitId, _ := seedTestRepo(t, repo) ref, err := repo.CreateReference("refs/heads/foo", commitId, true, "") @@ -151,7 +151,7 @@ func TestReferenceOwner(t *testing.T) { func TestUtil(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo) @@ -192,8 +192,7 @@ func checkRefType(t *testing.T, ref *Reference, kind ReferenceType) { // The failure happens at wherever we were called, not here _, file, line, ok := runtime.Caller(1) if !ok { - t.Fatal() + t.Fatalf("Unable to get caller") } - t.Fatalf("Wrong ref type at %v:%v; have %v, expected %v", file, line, ref.Type(), kind) } diff --git a/remote_test.go b/remote_test.go index bbbdeb9..23c80f5 100644 --- a/remote_test.go +++ b/remote_test.go @@ -2,14 +2,12 @@ package git import ( "fmt" - "os" "testing" ) func TestRefspecs(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) remote, err := repo.CreateAnonymousRemote("git://foo/bar", "refs/heads/*:refs/heads/*") checkFatal(t, err) @@ -30,8 +28,7 @@ func TestRefspecs(t *testing.T) { func TestListRemotes(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) _, err := repo.CreateRemote("test", "git://foo/bar") @@ -58,8 +55,7 @@ func assertHostname(cert *Certificate, valid bool, hostname string, t *testing.T func TestCertificateCheck(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) @@ -78,8 +74,7 @@ func TestCertificateCheck(t *testing.T) { func TestRemoteConnect(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) @@ -90,8 +85,7 @@ func TestRemoteConnect(t *testing.T) { func TestRemoteLs(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) @@ -109,8 +103,7 @@ func TestRemoteLs(t *testing.T) { func TestRemoteLsFiltering(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) @@ -136,8 +129,7 @@ func TestRemoteLsFiltering(t *testing.T) { func TestRemotePruneRefs(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) config, err := repo.Config() checkFatal(t, err) @@ -159,8 +151,7 @@ func TestRemotePruneRefs(t *testing.T) { func TestRemotePrune(t *testing.T) { remoteRepo := createTestRepo(t) - defer os.RemoveAll(remoteRepo.Workdir()) - defer remoteRepo.Free() + defer cleanupTestRepo(t, remoteRepo) head, _ := seedTestRepo(t, remoteRepo) commit, err := remoteRepo.LookupCommit(head) @@ -171,8 +162,7 @@ func TestRemotePrune(t *testing.T) { checkFatal(t, err) repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) - defer repo.Free() + defer cleanupTestRepo(t, repo) config, err := repo.Config() checkFatal(t, err) diff --git a/revparse_test.go b/revparse_test.go index 8c3a352..4bc327c 100644 --- a/revparse_test.go +++ b/revparse_test.go @@ -1,13 +1,12 @@ package git import ( - "os" "testing" ) func TestRevparse(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo) @@ -19,7 +18,7 @@ func TestRevparse(t *testing.T) { func TestRevparseSingle(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo) @@ -31,7 +30,7 @@ func TestRevparseSingle(t *testing.T) { func TestRevparseExt(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) _, treeId := seedTestRepo(t, repo) diff --git a/status_test.go b/status_test.go index d18fca1..5b97b00 100644 --- a/status_test.go +++ b/status_test.go @@ -2,15 +2,13 @@ package git import ( "io/ioutil" - "os" "path" "testing" ) func TestStatusFile(t *testing.T) { repo := createTestRepo(t) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) state := repo.State() if state != RepositoryStateNone { @@ -30,10 +28,10 @@ func TestStatusFile(t *testing.T) { func TestStatusList(t *testing.T) { repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + // This commits the test repo README, so it doesn't show up in the status list and there's a head to compare to seedTestRepo(t, repo) - defer repo.Free() - defer os.RemoveAll(repo.Workdir()) err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644) checkFatal(t, err) diff --git a/submodule.go b/submodule.go index fdd38cc..6edc1d7 100644 --- a/submodule.go +++ b/submodule.go @@ -96,17 +96,24 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { type SubmoduleCbk func(sub *Submodule, name string) int //export SubmoduleVisitor -func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, cfct unsafe.Pointer) C.int { +func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) C.int { sub := &Submodule{(*C.git_submodule)(csub)} - fct := *(*SubmoduleCbk)(cfct) - return (C.int)(fct(sub, C.GoString(name))) + + if callback, ok := pointerHandles.Get(handle).(SubmoduleCbk); ok { + return (C.int)(callback(sub, C.GoString(name))) + } else { + panic("invalid submodule visitor callback") + } } func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C._go_git_visit_submodule(repo.ptr, unsafe.Pointer(&cbk)) + handle := pointerHandles.Track(cbk) + defer pointerHandles.Untrack(handle) + + ret := C._go_git_visit_submodule(repo.ptr, handle) if ret < 0 { return MakeGitError(ret) } diff --git a/submodule_test.go b/submodule_test.go index 1c8f471..27bc193 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -6,6 +6,8 @@ import ( func TestSubmoduleForeach(t *testing.T) { repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + seedTestRepo(t, repo) _, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true) @@ -19,6 +21,6 @@ func TestSubmoduleForeach(t *testing.T) { checkFatal(t, err) if i != 1 { - t.Fatalf("expected one submodule found but got %i", i) + t.Fatalf("expected one submodule found but got %d", i) } } diff --git a/tag_test.go b/tag_test.go index 126cf6e..74f9fec 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1,14 +1,14 @@ package git import ( - "os" "testing" "time" ) func TestCreateTag(t *testing.T) { repo := createTestRepo(t) - defer os.RemoveAll(repo.Workdir()) + defer cleanupTestRepo(t, repo) + commitId, _ := seedTestRepo(t, repo) commit, err := repo.LookupCommit(commitId) diff --git a/tree.go b/tree.go index c18d02a..aad2c8d 100644 --- a/tree.go +++ b/tree.go @@ -90,22 +90,28 @@ func (t Tree) EntryCount() uint64 { type TreeWalkCallback func(string, *TreeEntry) int //export CallbackGitTreeWalk -func CallbackGitTreeWalk(_root unsafe.Pointer, _entry unsafe.Pointer, ptr unsafe.Pointer) C.int { - root := C.GoString((*C.char)(_root)) +func CallbackGitTreeWalk(_root *C.char, _entry unsafe.Pointer, ptr unsafe.Pointer) C.int { + root := C.GoString(_root) entry := (*C.git_tree_entry)(_entry) - callback := *(*TreeWalkCallback)(ptr) - return C.int(callback(root, newTreeEntry(entry))) + if callback, ok := pointerHandles.Get(ptr).(TreeWalkCallback); ok { + return C.int(callback(root, newTreeEntry(entry))) + } else { + panic("invalid treewalk callback") + } } func (t Tree) Walk(callback TreeWalkCallback) error { runtime.LockOSThread() defer runtime.UnlockOSThread() + ptr := pointerHandles.Track(callback) + defer pointerHandles.Untrack(ptr) + err := C._go_git_treewalk( t.cast_ptr, C.GIT_TREEWALK_PRE, - unsafe.Pointer(&callback), + ptr, ) if err < 0 {