More diff functionality #629
174
diff.go
|
@ -3,6 +3,7 @@ package git
|
||||||
/*
|
/*
|
||||||
#include <git2.h>
|
#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 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 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);
|
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
|
DiffFindRemoveUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO implement git_diff_similarity_metric
|
// TODO implement git_diff_similarity_metric
|
||||||
type DiffFindOptions struct {
|
type DiffFindOptions struct {
|
||||||
Flags DiffFindOptionsFlag
|
Flags DiffFindOptionsFlag
|
||||||
RenameThreshold uint16
|
RenameThreshold uint16
|
||||||
|
@ -847,3 +848,174 @@ func DiffBlobs(oldBlob *Blob, oldAsPath string, newBlob *Blob, newAsPath string,
|
||||||
|
|
||||||
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
![]() can this also use the same pattern as the other change?
can this also use the same pattern as the other change?
```go
if gitError, ok := err.(*GitError); ok {
return C.int(gitError.Code)
}
return -1
```
![]() Fixed Fixed
|
|||||||
|
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
|
||||||
|
}
|
||||||
![]() Can these be returned to their old place? The reason why they are needed is that when We may need to tweak Can these be returned to their old place? The reason why they are needed is that when [`runtime.LockOsThread()`](https://golang.org/pkg/runtime/#LockOSThread)/`runtime.UnlockOSThread()` combo is called, it asks Go to guarantee that _only_ this code executes on that thread (since Go is completely free to move stuff around when needed, at any point in time). That's the only way in which it can be guaranteed that if `git_apply_options_init` happens to place any error information in the Thread-local storage, `MakeGitError()` below can still find it.
We may need to tweak `script/check-MakeGitError-thread-lock.go` to _also_ complain if these functions don't happen before any cgo calls in the function to make this less error-prone.
![]() Fixed Fixed
|
|||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
![]() since there is no interaction with the thread-local storage in libgit2 (e.g. since there is no interaction with the thread-local storage in libgit2 (e.g. `git_error`-related stuff), this is not required here.
![]() Fixed Fixed
|
|||||||
|
|
||||||
|
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
|
||||||
![]()
```suggestion
runtime.KeepAlive(v)
runtime.KeepAlive(diff)
runtime.KeepAlive(cOpts)
```
![]() Fixed Fixed
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
307
diff_test.go
|
@ -2,6 +2,9 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -236,3 +239,307 @@ func TestDiffBlobs(t *testing.T) {
|
||||||
t.Fatalf("Bad number of lines iterated")
|
t.Fatalf("Bad number of lines iterated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyDiffAddfile(t *testing.T) {
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
addFirstFileCommit, addFileTree := addAndGetTree(t, repo, "file1", `hello`)
|
||||||
|
addSecondFileCommit, addSecondFileTree := addAndGetTree(t, repo, "file2", `hello2`)
|
||||||
|
|
||||||
|
diff, err := repo.DiffTreeToTree(addFileTree, addSecondFileTree, nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
t.Run("check does not apply to current tree because file exists", func(t *testing.T) {
|
||||||
|
err = repo.ResetToCommit(addSecondFileCommit, ResetHard, &CheckoutOpts{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expecting applying patch to current repo to fail")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check apply to correct commit", func(t *testing.T) {
|
||||||
|
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
t.Run("Check that diff only changed one file", func(t *testing.T) {
|
||||||
|
checkSecondFileStaged(t, repo)
|
||||||
|
|
||||||
|
index, err := repo.Index()
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer index.Free()
|
||||||
|
|
||||||
|
newTreeOID, err := index.WriteTreeTo(repo)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
newTree, err := repo.LookupTree(newTreeOID)
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer newTree.Free()
|
||||||
|
|
||||||
|
_, err = repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("patch apply"), newTree, addFirstFileCommit)
|
||||||
|
checkFatal(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test applying patch produced the same diff", func(t *testing.T) {
|
||||||
|
head, err := repo.Head()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
commit, err := repo.LookupCommit(head.Target())
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
tree, err := commit.Tree()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
newDiff, err := repo.DiffTreeToTree(addFileTree, tree, nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
raw1b, err := diff.ToBuf(DiffFormatPatch)
|
||||||
|
checkFatal(t, err)
|
||||||
|
raw2b, err := newDiff.ToBuf(DiffFormatPatch)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
raw1 := string(raw1b)
|
||||||
|
raw2 := string(raw2b)
|
||||||
|
|
||||||
|
if raw1 != raw2 {
|
||||||
|
t.Error("diffs should be the same")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check convert to raw buffer and apply", func(t *testing.T) {
|
||||||
|
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
raw, err := diff.ToBuf(DiffFormatPatch)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if len(raw) == 0 {
|
||||||
|
t.Error("empty diff created")
|
||||||
|
}
|
||||||
|
|
||||||
|
diff2, err := DiffFromBuffer(raw, repo)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff2, ApplyLocationBoth, nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check apply callbacks work", func(t *testing.T) {
|
||||||
|
// reset the state and get new default options for test
|
||||||
|
resetAndGetOpts := func(t *testing.T) *ApplyOptions {
|
||||||
|
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOpts{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
opts, err := DefaultApplyOptions()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Check hunk callback working applies patch", func(t *testing.T) {
|
||||||
|
opts := resetAndGetOpts(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) {
|
||||||
|
called = true
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Error("apply hunk callback was not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSecondFileStaged(t, repo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check delta callback working applies patch", func(t *testing.T) {
|
||||||
|
opts := resetAndGetOpts(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
|
||||||
|
if hunk.NewFile.Path != "file2" {
|
||||||
|
t.Error("Unexpected delta in diff application")
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Error("apply hunk callback was not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSecondFileStaged(t, repo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check delta callback returning false does not apply patch", func(t *testing.T) {
|
||||||
|
opts := resetAndGetOpts(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
|
||||||
|
if hunk.NewFile.Path != "file2" {
|
||||||
|
t.Error("Unexpected hunk in diff application")
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Error("apply hunk callback was not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNoFilesStaged(t, repo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check hunk callback returning causes application to fail", func(t *testing.T) {
|
||||||
|
opts := resetAndGetOpts(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) {
|
||||||
|
called = true
|
||||||
|
return false, errors.New("something happened")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected an error after trying to apply")
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Error("apply hunk callback was not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNoFilesStaged(t, repo)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check delta callback returning causes application to fail", func(t *testing.T) {
|
||||||
|
opts := resetAndGetOpts(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
|
||||||
|
if hunk.NewFile.Path != "file2" {
|
||||||
|
t.Error("Unexpected delta in diff application")
|
||||||
|
}
|
||||||
|
called = true
|
||||||
|
return false, errors.New("something happened")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected an error after trying to apply")
|
||||||
|
}
|
||||||
|
|
||||||
|
if called == false {
|
||||||
|
t.Error("apply hunk callback was not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNoFilesStaged(t, repo)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSecondFileStaged checks that there is a single file called "file2" uncommitted in the repo
|
||||||
|
func checkSecondFileStaged(t *testing.T, repo *Repository) {
|
||||||
|
opts := StatusOptions{
|
||||||
|
Show: StatusShowIndexAndWorkdir,
|
||||||
|
Flags: StatusOptIncludeUntracked,
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses, err := repo.StatusList(&opts)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
count, err := statuses.EntryCount()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if count != 1 {
|
||||||
|
t.Error("diff should affect exactly one file")
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
t.Fatal("no statuses, cannot continue test")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := statuses.ByIndex(0)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if entry.Status != StatusIndexNew {
|
||||||
|
t.Error("status should be 'new' as file has been added between commits")
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.HeadToIndex.NewFile.Path != "file2" {
|
||||||
|
t.Error("new file should be 'file2")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNoFilesStaged checks that there is a single file called "file2" uncommitted in the repo
|
||||||
|
func checkNoFilesStaged(t *testing.T, repo *Repository) {
|
||||||
|
opts := StatusOptions{
|
||||||
|
Show: StatusShowIndexAndWorkdir,
|
||||||
|
Flags: StatusOptIncludeUntracked,
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses, err := repo.StatusList(&opts)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
count, err := statuses.EntryCount()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if count != 0 {
|
||||||
|
t.Error("files changed unexpectedly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addAndGetTree creates a file and commits it, returning the commit and tree
|
||||||
|
func addAndGetTree(t *testing.T, repo *Repository, filename string, content string) (*Commit, *Tree) {
|
||||||
|
headCommit, err := headCommit(repo)
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer headCommit.Free()
|
||||||
|
|
||||||
|
p := repo.Path()
|
||||||
|
p = strings.TrimSuffix(p, ".git")
|
||||||
|
p = strings.TrimSuffix(p, ".git/")
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(path.Join(p, filename), []byte((content)), 0777)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
index, err := repo.Index()
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer index.Free()
|
||||||
|
|
||||||
|
err = index.AddByPath(filename)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
newTreeOID, err := index.WriteTreeTo(repo)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
newTree, err := repo.LookupTree(newTreeOID)
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer newTree.Free()
|
||||||
|
|
||||||
|
commitId, err := repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("add %s", filename), newTree, headCommit)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
commit, err := repo.LookupCommit(commitId)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
tree, err := commit.Tree()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
return commit, tree
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
|
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
|
||||||
|
|
||||||
|
void _go_git_populate_apply_cb(git_apply_options *options)
|
||||||
|
{
|
||||||
|
options->delta_cb = (git_apply_delta_cb)deltaApplyCallback;
|
||||||
|
options->hunk_cb = (git_apply_hunk_cb)hunkApplyCallback;
|
||||||
|
}
|
||||||
|
|
||||||
void _go_git_populate_remote_cb(git_clone_options *opts)
|
void _go_git_populate_remote_cb(git_clone_options *opts)
|
||||||
{
|
{
|
||||||
opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;
|
opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;
|
||||||
|
|
the
C.CString()
needs to beC.free()
d. Although it might be better to useC.CBytes()
since the buffer should be able to contain NULL bytes.Is it possible to avoid embedding the callbacks into this struct? otherwise it gives the appearance of there being an 'is-a' relationship between
ApplyOptions
andApplyHunkCallback
.can the
pointerHandles
only store the pointer to theApplyOptions
?other places (like
fc6eaf3638/diff.go (L514)
) use the function initialization rather than the initialization constant to avoid having to create a wrapper:can the
.toC()
call be outlined? all other places use thepattern (except for
Oid
, since those are just cast to unsafe pointers).the version is not needed to be exposed. it's only needed internally by libgit2 to ensure that the size of the struct is what it expects.
I exposed it because it is exposed in some of the other structs (For example, CherrypickOptions, RebaseOptions, etc). Should I just convert this to a non-exported field instead?
Fixed
I didn't realise that, I suppose that's why it passes the size as a separate parameter (so it can do a binary diff?) I'm not sure about the CString, I wasn't able to find out how Go behaves if theres a null byte in the middle of a string so I converted it to a CBytes as recommended
Fixed
I thought there was a reason I did it this way but after changing it to call this it works fine 🤔
I'd rather remove it altogether since that version number is just there to account for version drift in C, where there is no way to differentiate between different versions of the same struct. As long as there is a layer that accounts for this (e.g. git2go), it's an implementation detail that's best to hide from the end user.
RevertOptions was recently de-Versionified, so we should probably make the other Options follow suit:
4bca045e5a (diff-30188d21ac9afa73021c8dd3ae818448)
I'll probably do that at v31 branch creation time to avoid breaking the interface for older folks.the older name was better for consistency:
in L860-L861, the same name can be used for the field and the type, which is the way to express that something is not to be embedded.
(as mentioned above, this is possible)
Fixed
Removed and hardcoded like the other options