Skip to content

apply: Add bindings for git_apply_to_tree #657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 136 additions & 0 deletions diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"path"
"reflect"
"strings"
"testing"
)
Expand Down Expand Up @@ -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{
Expand Down
3 changes: 3 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand Down