diff --git a/diff.go b/diff.go index cd8793b..7f824fa 100644 --- a/diff.go +++ b/diff.go @@ -3,7 +3,6 @@ package git /* #include -extern void _go_git_populate_apply_cb(git_apply_options *options); extern int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload); extern void _go_git_setup_diff_notify_callbacks(git_diff_options* opts); extern int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload); @@ -551,7 +550,7 @@ const ( DiffFindRemoveUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED ) -// TODO implement git_diff_similarity_metric +//TODO implement git_diff_similarity_metric type DiffFindOptions struct { Flags DiffFindOptionsFlag RenameThreshold uint16 @@ -848,174 +847,3 @@ func DiffBlobs(oldBlob *Blob, oldAsPath string, newBlob *Blob, newAsPath string, return nil } - -// ApplyHunkCallback is a callback that will be made per delta (file) when applying a patch. -type ApplyHunkCallback func(*DiffHunk) (apply bool, err error) - -// ApplyDeltaCallback is a callback that will be made per hunk when applying a patch. -type ApplyDeltaCallback func(*DiffDelta) (apply bool, err error) - -// ApplyOptions has 2 callbacks that are called for hunks or deltas -// If these functions return an error, abort the apply process immediately. -// If the first return value is true, the delta/hunk will be applied. If it is false, the delta/hunk will not be applied. In either case, the rest of the apply process will continue. -type ApplyOptions struct { - ApplyHunkCallback ApplyHunkCallback - ApplyDeltaCallback ApplyDeltaCallback - Flags uint -} - -//export hunkApplyCallback -func hunkApplyCallback(_hunk *C.git_diff_hunk, _payload unsafe.Pointer) C.int { - opts, ok := pointerHandles.Get(_payload).(*ApplyOptions) - if !ok { - panic("invalid apply options payload") - } - - if opts.ApplyHunkCallback == nil { - return 0 - } - - hunk := diffHunkFromC(_hunk) - - apply, err := opts.ApplyHunkCallback(&hunk) - if err != nil { - if gitError, ok := err.(*GitError); ok { - return C.int(gitError.Code) - } - return -1 - } else if apply { - return 0 - } else { - return 1 - } -} - -//export deltaApplyCallback -func deltaApplyCallback(_delta *C.git_diff_delta, _payload unsafe.Pointer) C.int { - opts, ok := pointerHandles.Get(_payload).(*ApplyOptions) - if !ok { - panic("invalid apply options payload") - } - - if opts.ApplyDeltaCallback == nil { - return 0 - } - - delta := diffDeltaFromC(_delta) - - apply, err := opts.ApplyDeltaCallback(&delta) - if err != nil { - if gitError, ok := err.(*GitError); ok { - return C.int(gitError.Code) - } - return -1 - } else if apply { - return 0 - } else { - return 1 - } -} - -// DefaultApplyOptions returns default options for applying diffs or patches. -func DefaultApplyOptions() (*ApplyOptions, error) { - opts := C.git_apply_options{} - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_apply_options_init(&opts, C.GIT_APPLY_OPTIONS_VERSION) - if int(ecode) != 0 { - - return nil, MakeGitError(ecode) - } - - return applyOptionsFromC(&opts), nil -} - -func (a *ApplyOptions) toC() *C.git_apply_options { - if a == nil { - return nil - } - - opts := &C.git_apply_options{ - version: C.GIT_APPLY_OPTIONS_VERSION, - flags: C.uint(a.Flags), - } - - if a.ApplyDeltaCallback != nil || a.ApplyHunkCallback != nil { - C._go_git_populate_apply_cb(opts) - opts.payload = pointerHandles.Track(a) - } - - return opts -} - -func applyOptionsFromC(opts *C.git_apply_options) *ApplyOptions { - return &ApplyOptions{ - Flags: uint(opts.flags), - } -} - -// ApplyLocation represents the possible application locations for applying -// diffs. -type ApplyLocation int - -const ( - // ApplyLocationWorkdir applies the patch to the workdir, leaving the - // index untouched. This is the equivalent of `git apply` with no location - // argument. - ApplyLocationWorkdir ApplyLocation = C.GIT_APPLY_LOCATION_WORKDIR - // ApplyLocationIndex applies the patch to the index, leaving the working - // directory untouched. This is the equivalent of `git apply --cached`. - ApplyLocationIndex ApplyLocation = C.GIT_APPLY_LOCATION_INDEX - // ApplyLocationBoth applies the patch to both the working directory and - // the index. This is the equivalent of `git apply --index`. - ApplyLocationBoth ApplyLocation = C.GIT_APPLY_LOCATION_BOTH -) - -// ApplyDiff appllies a Diff to the given repository, making changes directly -// in the working directory, the index, or both. -func (v *Repository) ApplyDiff(diff *Diff, location ApplyLocation, opts *ApplyOptions) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - cOpts := opts.toC() - ecode := C.git_apply(v.ptr, diff.ptr, C.git_apply_location_t(location), cOpts) - runtime.KeepAlive(v) - runtime.KeepAlive(diff) - runtime.KeepAlive(cOpts) - if ecode < 0 { - return MakeGitError(ecode) - } - - return nil -} - -// DiffFromBuffer reads the contents of a git patch file into a Diff object. -// -// The diff object produced is similar to the one that would be produced if you -// actually produced it computationally by comparing two trees, however there -// may be subtle differences. For example, a patch file likely contains -// abbreviated object IDs, so the object IDs in a git_diff_delta produced by -// this function will also be abbreviated. -// -// This function will only read patch files created by a git implementation, it -// will not read unified diffs produced by the diff program, nor any other -// types of patch files. -func DiffFromBuffer(buffer []byte, repo *Repository) (*Diff, error) { - var diff *C.git_diff - - cBuffer := C.CBytes(buffer) - defer C.free(unsafe.Pointer(cBuffer)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_diff_from_buffer(&diff, (*C.char)(cBuffer), C.size_t(len(buffer))) - if ecode < 0 { - return nil, MakeGitError(ecode) - } - runtime.KeepAlive(diff) - - return newDiffFromC(diff, repo), nil -} diff --git a/diff_test.go b/diff_test.go index 394a4c1..6fbad51 100644 --- a/diff_test.go +++ b/diff_test.go @@ -2,9 +2,6 @@ package git import ( "errors" - "fmt" - "io/ioutil" - "path" "strings" "testing" ) @@ -239,307 +236,3 @@ func TestDiffBlobs(t *testing.T) { t.Fatalf("Bad number of lines iterated") } } - -func TestApplyDiffAddfile(t *testing.T) { - repo := createTestRepo(t) - defer cleanupTestRepo(t, repo) - - seedTestRepo(t, repo) - - addFirstFileCommit, addFileTree := addAndGetTree(t, repo, "file1", `hello`) - addSecondFileCommit, addSecondFileTree := addAndGetTree(t, repo, "file2", `hello2`) - - diff, err := repo.DiffTreeToTree(addFileTree, addSecondFileTree, nil) - checkFatal(t, err) - - t.Run("check does not apply to current tree because file exists", func(t *testing.T) { - err = repo.ResetToCommit(addSecondFileCommit, ResetHard, &CheckoutOpts{}) - checkFatal(t, err) - - err = repo.ApplyDiff(diff, ApplyLocationBoth, nil) - if err == nil { - t.Error("expecting applying patch to current repo to fail") - } - }) - - t.Run("check apply to correct commit", func(t *testing.T) { - err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{}) - checkFatal(t, err) - - err = repo.ApplyDiff(diff, ApplyLocationBoth, nil) - checkFatal(t, err) - - t.Run("Check that diff only changed one file", func(t *testing.T) { - checkSecondFileStaged(t, repo) - - index, err := repo.Index() - checkFatal(t, err) - defer index.Free() - - newTreeOID, err := index.WriteTreeTo(repo) - checkFatal(t, err) - - newTree, err := repo.LookupTree(newTreeOID) - checkFatal(t, err) - defer newTree.Free() - - _, err = repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("patch apply"), newTree, addFirstFileCommit) - checkFatal(t, err) - }) - - t.Run("test applying patch produced the same diff", func(t *testing.T) { - head, err := repo.Head() - checkFatal(t, err) - - commit, err := repo.LookupCommit(head.Target()) - checkFatal(t, err) - - tree, err := commit.Tree() - checkFatal(t, err) - - newDiff, err := repo.DiffTreeToTree(addFileTree, tree, nil) - checkFatal(t, err) - - raw1b, err := diff.ToBuf(DiffFormatPatch) - checkFatal(t, err) - raw2b, err := newDiff.ToBuf(DiffFormatPatch) - checkFatal(t, err) - - raw1 := string(raw1b) - raw2 := string(raw2b) - - if raw1 != raw2 { - t.Error("diffs should be the same") - } - }) - }) - - t.Run("check convert to raw buffer and apply", func(t *testing.T) { - err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{}) - checkFatal(t, err) - - raw, err := diff.ToBuf(DiffFormatPatch) - checkFatal(t, err) - - if len(raw) == 0 { - t.Error("empty diff created") - } - - diff2, err := DiffFromBuffer(raw, repo) - checkFatal(t, err) - - err = repo.ApplyDiff(diff2, ApplyLocationBoth, nil) - checkFatal(t, err) - }) - - t.Run("check apply callbacks work", func(t *testing.T) { - // reset the state and get new default options for test - resetAndGetOpts := func(t *testing.T) *ApplyOptions { - err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{}) - checkFatal(t, err) - - opts, err := DefaultApplyOptions() - checkFatal(t, err) - - return opts - } - - t.Run("Check hunk callback working applies patch", func(t *testing.T) { - opts := resetAndGetOpts(t) - - called := false - opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) { - called = true - return true, nil - } - - err = repo.ApplyDiff(diff, ApplyLocationBoth, opts) - checkFatal(t, err) - - if called == false { - t.Error("apply hunk callback was not called") - } - - checkSecondFileStaged(t, repo) - }) - - t.Run("Check delta callback working applies patch", func(t *testing.T) { - opts := resetAndGetOpts(t) - - called := false - opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) { - if hunk.NewFile.Path != "file2" { - t.Error("Unexpected delta in diff application") - } - called = true - return true, nil - } - - err = repo.ApplyDiff(diff, ApplyLocationBoth, opts) - checkFatal(t, err) - - if called == false { - t.Error("apply hunk callback was not called") - } - - checkSecondFileStaged(t, repo) - }) - - t.Run("Check delta callback returning false does not apply patch", func(t *testing.T) { - opts := resetAndGetOpts(t) - - called := false - opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) { - if hunk.NewFile.Path != "file2" { - t.Error("Unexpected hunk in diff application") - } - called = true - return false, nil - } - - err = repo.ApplyDiff(diff, ApplyLocationBoth, opts) - checkFatal(t, err) - - if called == false { - t.Error("apply hunk callback was not called") - } - - checkNoFilesStaged(t, repo) - }) - - t.Run("Check hunk callback returning causes application to fail", func(t *testing.T) { - opts := resetAndGetOpts(t) - - called := false - opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) { - called = true - return false, errors.New("something happened") - } - - err = repo.ApplyDiff(diff, ApplyLocationBoth, opts) - if err == nil { - t.Error("expected an error after trying to apply") - } - - if called == false { - t.Error("apply hunk callback was not called") - } - - checkNoFilesStaged(t, repo) - }) - - t.Run("Check delta callback returning causes application to fail", func(t *testing.T) { - opts := resetAndGetOpts(t) - - called := false - opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) { - if hunk.NewFile.Path != "file2" { - t.Error("Unexpected delta in diff application") - } - called = true - return false, errors.New("something happened") - } - - err = repo.ApplyDiff(diff, ApplyLocationBoth, opts) - if err == nil { - t.Error("expected an error after trying to apply") - } - - if called == false { - t.Error("apply hunk callback was not called") - } - - checkNoFilesStaged(t, repo) - }) - }) -} - -// checkSecondFileStaged checks that there is a single file called "file2" uncommitted in the repo -func checkSecondFileStaged(t *testing.T, repo *Repository) { - opts := StatusOptions{ - Show: StatusShowIndexAndWorkdir, - Flags: StatusOptIncludeUntracked, - } - - statuses, err := repo.StatusList(&opts) - checkFatal(t, err) - - count, err := statuses.EntryCount() - checkFatal(t, err) - - if count != 1 { - t.Error("diff should affect exactly one file") - } - if count == 0 { - t.Fatal("no statuses, cannot continue test") - } - - entry, err := statuses.ByIndex(0) - checkFatal(t, err) - - if entry.Status != StatusIndexNew { - t.Error("status should be 'new' as file has been added between commits") - } - - if entry.HeadToIndex.NewFile.Path != "file2" { - t.Error("new file should be 'file2") - } - return -} - -// checkNoFilesStaged checks that there is a single file called "file2" uncommitted in the repo -func checkNoFilesStaged(t *testing.T, repo *Repository) { - opts := StatusOptions{ - Show: StatusShowIndexAndWorkdir, - Flags: StatusOptIncludeUntracked, - } - - statuses, err := repo.StatusList(&opts) - checkFatal(t, err) - - count, err := statuses.EntryCount() - checkFatal(t, err) - - if count != 0 { - t.Error("files changed unexpectedly") - } -} - -// addAndGetTree creates a file and commits it, returning the commit and tree -func addAndGetTree(t *testing.T, repo *Repository, filename string, content string) (*Commit, *Tree) { - headCommit, err := headCommit(repo) - checkFatal(t, err) - defer headCommit.Free() - - p := repo.Path() - p = strings.TrimSuffix(p, ".git") - p = strings.TrimSuffix(p, ".git/") - - err = ioutil.WriteFile(path.Join(p, filename), []byte((content)), 0777) - checkFatal(t, err) - - index, err := repo.Index() - checkFatal(t, err) - defer index.Free() - - err = index.AddByPath(filename) - checkFatal(t, err) - - newTreeOID, err := index.WriteTreeTo(repo) - checkFatal(t, err) - - newTree, err := repo.LookupTree(newTreeOID) - checkFatal(t, err) - defer newTree.Free() - - commitId, err := repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("add %s", filename), newTree, headCommit) - checkFatal(t, err) - - commit, err := repo.LookupCommit(commitId) - checkFatal(t, err) - - tree, err := commit.Tree() - checkFatal(t, err) - - return commit, tree -} diff --git a/wrapper.c b/wrapper.c index 1093cf4..df767fb 100644 --- a/wrapper.c +++ b/wrapper.c @@ -5,12 +5,6 @@ typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); -void _go_git_populate_apply_cb(git_apply_options *options) -{ - options->delta_cb = (git_apply_delta_cb)deltaApplyCallback; - options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback; -} - void _go_git_populate_remote_cb(git_clone_options *opts) { opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;