summaryrefslogtreecommitdiff
path: root/diff.go
diff options
context:
space:
mode:
authormichael boulton <[email protected]>2020-08-18 14:14:02 +0100
committerGitHub <[email protected]>2020-08-18 06:14:02 -0700
commit7883ec85de56ee55667481228282fd690fce6246 (patch)
tree8a686eb37f0b79d2a81b05bb50e8cfcc292b796e /diff.go
parent2ac9f4e69bd57a686d15176d199a3c9cc4a6bb91 (diff)
More diff functionality (#629)
This PR adds - The ability to apply a Diff object to the repo - Support for git_apply_hunk_cb and git_apply_delta_cb callbacks in options for applying the diffs - The ability to import a diff from a raw buffer (for example, one exported by ToBuf) into a Diff object associated with the repo - Tests for the above
Diffstat (limited to 'diff.go')
-rw-r--r--diff.go174
1 files changed, 173 insertions, 1 deletions
diff --git a/diff.go b/diff.go
index e022b47..9d17dd7 100644
--- a/diff.go
+++ b/diff.go
@@ -3,6 +3,7 @@ package git
/*
#include <git2.h>
+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);
@@ -550,7 +551,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
@@ -847,3 +848,174 @@ 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
+}