summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--diff.go18
-rw-r--r--diff_test.go136
-rw-r--r--git.go3
3 files changed, 157 insertions, 0 deletions
diff --git a/diff.go b/diff.go
index f65a2dd..76838e7 100644
--- a/diff.go
+++ b/diff.go
@@ -991,6 +991,24 @@ func (v *Repository) ApplyDiff(diff *Diff, location ApplyLocation, opts *ApplyOp
return nil
}
+// ApplyToTree applies a Diff to a Tree and returns the resulting image as an Index.
+func (v *Repository) ApplyToTree(diff *Diff, tree *Tree, opts *ApplyOptions) (*Index, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ var indexPtr *C.git_index
+ cOpts := opts.toC()
+ ecode := C.git_apply_to_tree(&indexPtr, v.ptr, tree.cast_ptr, diff.ptr, cOpts)
+ runtime.KeepAlive(diff)
+ runtime.KeepAlive(tree)
+ runtime.KeepAlive(cOpts)
+ if ecode != 0 {
+ return nil, MakeGitError(ecode)
+ }
+
+ return newIndexFromC(indexPtr, v), 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
diff --git a/diff_test.go b/diff_test.go
index e440206..e2f810b 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"path"
+ "reflect"
"strings"
"testing"
)
@@ -463,6 +464,141 @@ func TestApplyDiffAddfile(t *testing.T) {
})
}
+func TestApplyToTree(t *testing.T) {
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ seedTestRepo(t, repo)
+
+ commitA, treeA := addAndGetTree(t, repo, "file", "a")
+ defer commitA.Free()
+ defer treeA.Free()
+ commitB, treeB := addAndGetTree(t, repo, "file", "b")
+ defer commitB.Free()
+ defer treeB.Free()
+ commitC, treeC := addAndGetTree(t, repo, "file", "c")
+ defer commitC.Free()
+ defer treeC.Free()
+
+ diffAB, err := repo.DiffTreeToTree(treeA, treeB, nil)
+ checkFatal(t, err)
+
+ diffAC, err := repo.DiffTreeToTree(treeA, treeC, nil)
+ checkFatal(t, err)
+
+ for _, tc := range []struct {
+ name string
+ tree *Tree
+ diff *Diff
+ applyHunkCallback ApplyHunkCallback
+ applyDeltaCallback ApplyDeltaCallback
+ error error
+ expectedDiff *Diff
+ }{
+ {
+ name: "applying patch produces the same diff",
+ tree: treeA,
+ diff: diffAB,
+ expectedDiff: diffAB,
+ },
+ {
+ name: "applying a conflicting patch errors",
+ tree: treeB,
+ diff: diffAC,
+ error: &GitError{
+ Message: "hunk at line 1 did not apply",
+ Code: ErrApplyFail,
+ Class: ErrClassPatch,
+ },
+ },
+ {
+ name: "callbacks succeeding apply the diff",
+ tree: treeA,
+ diff: diffAB,
+ applyHunkCallback: func(*DiffHunk) (bool, error) { return true, nil },
+ applyDeltaCallback: func(*DiffDelta) (bool, error) { return true, nil },
+ expectedDiff: diffAB,
+ },
+ {
+ name: "hunk callback returning false does not apply",
+ tree: treeA,
+ diff: diffAB,
+ applyHunkCallback: func(*DiffHunk) (bool, error) { return false, nil },
+ },
+ {
+ name: "hunk callback erroring fails the call",
+ tree: treeA,
+ diff: diffAB,
+ applyHunkCallback: func(*DiffHunk) (bool, error) { return true, errors.New("message dropped") },
+ error: &GitError{
+ Code: ErrGeneric,
+ Class: ErrClassInvalid,
+ },
+ },
+ {
+ name: "delta callback returning false does not apply",
+ tree: treeA,
+ diff: diffAB,
+ applyDeltaCallback: func(*DiffDelta) (bool, error) { return false, nil },
+ },
+ {
+ name: "delta callback erroring fails the call",
+ tree: treeA,
+ diff: diffAB,
+ applyDeltaCallback: func(*DiffDelta) (bool, error) { return true, errors.New("message dropped") },
+ error: &GitError{
+ Code: ErrGeneric,
+ Class: ErrClassInvalid,
+ },
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ opts, err := DefaultApplyOptions()
+ checkFatal(t, err)
+
+ opts.ApplyHunkCallback = tc.applyHunkCallback
+ opts.ApplyDeltaCallback = tc.applyDeltaCallback
+
+ index, err := repo.ApplyToTree(tc.diff, tc.tree, opts)
+ if tc.error != nil {
+ if !reflect.DeepEqual(err, tc.error) {
+ t.Fatalf("expected error %q but got %q", tc.error, err)
+ }
+
+ return
+ }
+ checkFatal(t, err)
+
+ patchedTreeOID, err := index.WriteTreeTo(repo)
+ checkFatal(t, err)
+
+ patchedTree, err := repo.LookupTree(patchedTreeOID)
+ checkFatal(t, err)
+
+ patchedDiff, err := repo.DiffTreeToTree(tc.tree, patchedTree, nil)
+ checkFatal(t, err)
+
+ appliedRaw, err := patchedDiff.ToBuf(DiffFormatPatch)
+ checkFatal(t, err)
+
+ if tc.expectedDiff == nil {
+ if len(appliedRaw) > 0 {
+ t.Fatalf("expected no diff but got: %s", appliedRaw)
+ }
+
+ return
+ }
+
+ expectedDiff, err := tc.expectedDiff.ToBuf(DiffFormatPatch)
+ checkFatal(t, err)
+
+ if string(expectedDiff) != string(appliedRaw) {
+ t.Fatalf("diffs do not match:\nexpected: %s\n\nactual: %s", expectedDiff, appliedRaw)
+ }
+ })
+ }
+}
+
// checkSecondFileStaged checks that there is a single file called "file2" uncommitted in the repo
func checkSecondFileStaged(t *testing.T, repo *Repository) {
opts := StatusOptions{
diff --git a/git.go b/git.go
index 5acff0d..d3def5e 100644
--- a/git.go
+++ b/git.go
@@ -45,6 +45,7 @@ const (
ErrClassRevert ErrorClass = C.GITERR_REVERT
ErrClassCallback ErrorClass = C.GITERR_CALLBACK
ErrClassRebase ErrorClass = C.GITERR_REBASE
+ ErrClassPatch ErrorClass = C.GITERR_PATCH
)
type ErrorCode int
@@ -109,6 +110,8 @@ const (
ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH
// Signals end of iteration with iterator
ErrIterOver ErrorCode = C.GIT_ITEROVER
+ // Patch application failed
+ ErrApplyFail ErrorCode = C.GIT_EAPPLYFAIL
)
var (