apply: Add bindings for git_apply_to_tree (#657) #665
18
diff.go
18
diff.go
|
@ -980,6 +980,24 @@ func (v *Repository) ApplyDiff(diff *Diff, location ApplyLocation, opts *ApplyOp
|
||||||
return nil
|
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.
|
// 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
|
// The diff object produced is similar to the one that would be produced if you
|
||||||
|
|
136
diff_test.go
136
diff_test.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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
|
// checkSecondFileStaged checks that there is a single file called "file2" uncommitted in the repo
|
||||||
func checkSecondFileStaged(t *testing.T, repo *Repository) {
|
func checkSecondFileStaged(t *testing.T, repo *Repository) {
|
||||||
opts := StatusOptions{
|
opts := StatusOptions{
|
||||||
|
|
3
git.go
3
git.go
|
@ -45,6 +45,7 @@ const (
|
||||||
ErrClassRevert ErrorClass = C.GITERR_REVERT
|
ErrClassRevert ErrorClass = C.GITERR_REVERT
|
||||||
ErrClassCallback ErrorClass = C.GITERR_CALLBACK
|
ErrClassCallback ErrorClass = C.GITERR_CALLBACK
|
||||||
ErrClassRebase ErrorClass = C.GITERR_REBASE
|
ErrClassRebase ErrorClass = C.GITERR_REBASE
|
||||||
|
ErrClassPatch ErrorClass = C.GITERR_PATCH
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorCode int
|
type ErrorCode int
|
||||||
|
@ -109,6 +110,8 @@ const (
|
||||||
ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH
|
ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH
|
||||||
// Signals end of iteration with iterator
|
// Signals end of iteration with iterator
|
||||||
ErrIterOver ErrorCode = C.GIT_ITEROVER
|
ErrIterOver ErrorCode = C.GIT_ITEROVER
|
||||||
|
// Patch application failed
|
||||||
|
ErrApplyFail ErrorCode = C.GIT_EAPPLYFAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in New Issue