Skip to content

Commit 7883ec8

Browse files
authored
More diff functionality (libgit2#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
1 parent 2ac9f4e commit 7883ec8

File tree

3 files changed

+486
-1
lines changed

3 files changed

+486
-1
lines changed

diff.go

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package git
33
/*
44
#include <git2.h>
55
6+
extern void _go_git_populate_apply_cb(git_apply_options *options);
67
extern int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLine, void *payload);
78
extern void _go_git_setup_diff_notify_callbacks(git_diff_options* opts);
89
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 (
550551
DiffFindRemoveUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED
551552
)
552553

553-
//TODO implement git_diff_similarity_metric
554+
// TODO implement git_diff_similarity_metric
554555
type DiffFindOptions struct {
555556
Flags DiffFindOptionsFlag
556557
RenameThreshold uint16
@@ -847,3 +848,174 @@ func DiffBlobs(oldBlob *Blob, oldAsPath string, newBlob *Blob, newAsPath string,
847848

848849
return nil
849850
}
851+
852+
// ApplyHunkCallback is a callback that will be made per delta (file) when applying a patch.
853+
type ApplyHunkCallback func(*DiffHunk) (apply bool, err error)
854+
855+
// ApplyDeltaCallback is a callback that will be made per hunk when applying a patch.
856+
type ApplyDeltaCallback func(*DiffDelta) (apply bool, err error)
857+
858+
// ApplyOptions has 2 callbacks that are called for hunks or deltas
859+
// If these functions return an error, abort the apply process immediately.
860+
// 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.
861+
type ApplyOptions struct {
862+
ApplyHunkCallback ApplyHunkCallback
863+
ApplyDeltaCallback ApplyDeltaCallback
864+
Flags uint
865+
}
866+
867+
//export hunkApplyCallback
868+
func hunkApplyCallback(_hunk *C.git_diff_hunk, _payload unsafe.Pointer) C.int {
869+
opts, ok := pointerHandles.Get(_payload).(*ApplyOptions)
870+
if !ok {
871+
panic("invalid apply options payload")
872+
}
873+
874+
if opts.ApplyHunkCallback == nil {
875+
return 0
876+
}
877+
878+
hunk := diffHunkFromC(_hunk)
879+
880+
apply, err := opts.ApplyHunkCallback(&hunk)
881+
if err != nil {
882+
if gitError, ok := err.(*GitError); ok {
883+
return C.int(gitError.Code)
884+
}
885+
return -1
886+
} else if apply {
887+
return 0
888+
} else {
889+
return 1
890+
}
891+
}
892+
893+
//export deltaApplyCallback
894+
func deltaApplyCallback(_delta *C.git_diff_delta, _payload unsafe.Pointer) C.int {
895+
opts, ok := pointerHandles.Get(_payload).(*ApplyOptions)
896+
if !ok {
897+
panic("invalid apply options payload")
898+
}
899+
900+
if opts.ApplyDeltaCallback == nil {
901+
return 0
902+
}
903+
904+
delta := diffDeltaFromC(_delta)
905+
906+
apply, err := opts.ApplyDeltaCallback(&delta)
907+
if err != nil {
908+
if gitError, ok := err.(*GitError); ok {
909+
return C.int(gitError.Code)
910+
}
911+
return -1
912+
} else if apply {
913+
return 0
914+
} else {
915+
return 1
916+
}
917+
}
918+
919+
// DefaultApplyOptions returns default options for applying diffs or patches.
920+
func DefaultApplyOptions() (*ApplyOptions, error) {
921+
opts := C.git_apply_options{}
922+
923+
runtime.LockOSThread()
924+
defer runtime.UnlockOSThread()
925+
926+
ecode := C.git_apply_options_init(&opts, C.GIT_APPLY_OPTIONS_VERSION)
927+
if int(ecode) != 0 {
928+
929+
return nil, MakeGitError(ecode)
930+
}
931+
932+
return applyOptionsFromC(&opts), nil
933+
}
934+
935+
func (a *ApplyOptions) toC() *C.git_apply_options {
936+
if a == nil {
937+
return nil
938+
}
939+
940+
opts := &C.git_apply_options{
941+
version: C.GIT_APPLY_OPTIONS_VERSION,
942+
flags: C.uint(a.Flags),
943+
}
944+
945+
if a.ApplyDeltaCallback != nil || a.ApplyHunkCallback != nil {
946+
C._go_git_populate_apply_cb(opts)
947+
opts.payload = pointerHandles.Track(a)
948+
}
949+
950+
return opts
951+
}
952+
953+
func applyOptionsFromC(opts *C.git_apply_options) *ApplyOptions {
954+
return &ApplyOptions{
955+
Flags: uint(opts.flags),
956+
}
957+
}
958+
959+
// ApplyLocation represents the possible application locations for applying
960+
// diffs.
961+
type ApplyLocation int
962+
963+
const (
964+
// ApplyLocationWorkdir applies the patch to the workdir, leaving the
965+
// index untouched. This is the equivalent of `git apply` with no location
966+
// argument.
967+
ApplyLocationWorkdir ApplyLocation = C.GIT_APPLY_LOCATION_WORKDIR
968+
// ApplyLocationIndex applies the patch to the index, leaving the working
969+
// directory untouched. This is the equivalent of `git apply --cached`.
970+
ApplyLocationIndex ApplyLocation = C.GIT_APPLY_LOCATION_INDEX
971+
// ApplyLocationBoth applies the patch to both the working directory and
972+
// the index. This is the equivalent of `git apply --index`.
973+
ApplyLocationBoth ApplyLocation = C.GIT_APPLY_LOCATION_BOTH
974+
)
975+
976+
// ApplyDiff appllies a Diff to the given repository, making changes directly
977+
// in the working directory, the index, or both.
978+
func (v *Repository) ApplyDiff(diff *Diff, location ApplyLocation, opts *ApplyOptions) error {
979+
runtime.LockOSThread()
980+
defer runtime.UnlockOSThread()
981+
982+
cOpts := opts.toC()
983+
ecode := C.git_apply(v.ptr, diff.ptr, C.git_apply_location_t(location), cOpts)
984+
runtime.KeepAlive(v)
985+
runtime.KeepAlive(diff)
986+
runtime.KeepAlive(cOpts)
987+
if ecode < 0 {
988+
return MakeGitError(ecode)
989+
}
990+
991+
return nil
992+
}
993+
994+
// DiffFromBuffer reads the contents of a git patch file into a Diff object.
995+
//
996+
// The diff object produced is similar to the one that would be produced if you
997+
// actually produced it computationally by comparing two trees, however there
998+
// may be subtle differences. For example, a patch file likely contains
999+
// abbreviated object IDs, so the object IDs in a git_diff_delta produced by
1000+
// this function will also be abbreviated.
1001+
//
1002+
// This function will only read patch files created by a git implementation, it
1003+
// will not read unified diffs produced by the diff program, nor any other
1004+
// types of patch files.
1005+
func DiffFromBuffer(buffer []byte, repo *Repository) (*Diff, error) {
1006+
var diff *C.git_diff
1007+
1008+
cBuffer := C.CBytes(buffer)
1009+
defer C.free(unsafe.Pointer(cBuffer))
1010+
1011+
runtime.LockOSThread()
1012+
defer runtime.UnlockOSThread()
1013+
1014+
ecode := C.git_diff_from_buffer(&diff, (*C.char)(cBuffer), C.size_t(len(buffer)))
1015+
if ecode < 0 {
1016+
return nil, MakeGitError(ecode)
1017+
}
1018+
runtime.KeepAlive(diff)
1019+
1020+
return newDiffFromC(diff, repo), nil
1021+
}

0 commit comments

Comments
 (0)