diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000..4337fb1 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,53 @@ +name: Backport to older releases +on: + push: + branches: + - master + +jobs: + + backport: + strategy: + fail-fast: false + matrix: + branch: [ 'release-0.28', 'release-0.27' ] + name: Backport change to branch ${{ matrix.branch }} + + runs-on: ubuntu-20.04 + + steps: + - name: Check out code + uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: Create a cherry-pick PR + run: | + if ! git diff --quiet HEAD^ HEAD -- vendor/libgit2; then + echo '::warning::Skipping cherry-pick since it is a vendored libgit2 bump' + exit 0 + fi + + BRANCH_NAME="cherry-pick-${{ github.run_id }}-${{ matrix.branch }}" + + # Setup usernames and authentication + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + cat <<- EOF > $HOME/.netrc + machine github.com + login ${{ github.actor }} + password ${{ secrets.GITHUB_TOKEN }} + machine api.github.com + login ${{ github.actor }} + password ${{ secrets.GITHUB_TOKEN }} + EOF + chmod 600 $HOME/.netrc + + # Create the cherry-pick commit and create the PR for it. + git checkout "${{ matrix.branch }}" + git switch -c "${BRANCH_NAME}" + git cherry-pick -x "${{ github.sha }}" + git push --set-upstream origin "${BRANCH_NAME}" + GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}" gh pr create \ + --base "${{ matrix.branch }}" \ + --title "$(git --no-pager show --format="%s" --no-patch HEAD)" \ + --body "$(git --no-pager show --format="%b" --no-patch HEAD)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebbc9b0..42636bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,10 +46,10 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.11', '1.12', '1.13' ] + go: [ '1.11', '1.12', '1.13', '1.14' ] name: Go ${{ matrix.go }} - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Set up Go @@ -71,13 +71,13 @@ jobs: fail-fast: false name: Go (dynamic) - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: '1.13' + go-version: '1.14' id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 @@ -87,3 +87,47 @@ jobs: make build-libgit2-dynamic - name: Test run: make test-dynamic + + build-system-dynamic: + strategy: + fail-fast: false + name: Go (system-wide, dynamic) + + runs-on: ubuntu-20.04 + + steps: + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: '1.14' + id: go + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + - name: Build libgit2 + run: | + git submodule update --init + sudo ./script/build-libgit2.sh --dynamic --system + - name: Test + run: make test + + build-system-static: + strategy: + fail-fast: false + name: Go (system-wide, static) + + runs-on: ubuntu-20.04 + + steps: + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: '1.14' + id: go + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + - name: Build libgit2 + run: | + git submodule update --init + sudo ./script/build-libgit2.sh --static --system + - name: Test + run: go test --count=1 --tags "static,system_libgit2" ./... diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..b293274 --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,28 @@ +name: Tag new releases +on: + push: + branches: + - master + - release-* + +jobs: + + tag-release: + name: Bump tag in ${{ github.ref }} + + runs-on: ubuntu-20.04 + + steps: + - name: Check out code + uses: actions/checkout@v1 + with: + fetch-depth: 0 + - name: Bump version and push tag + id: bump-version + uses: anothrNick/github-tag-action@9aaabdb5e989894e95288328d8b17a6347217ae3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: true + DEFAULT_BUMP: patch + TAG_CONTEXT: branch + RELEASE_BRANCHES: .* diff --git a/README.md b/README.md index 05dddec..79a96e9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The `master` branch follows the tip of libgit2 itself (with some lag) and as suc ### Which branch to send Pull requests to -If there's something version-specific that you'd want to contribute to, you can send them to the `release-${MAJOR}-${MINOR}` branches, which follow libgit2's releases. +If there's something version-specific that you'd want to contribute to, you can send them to the `release-${MAJOR}.${MINOR}` branches, which follow libgit2's releases. Installing ---------- 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 +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 +} diff --git a/diff_test.go b/diff_test.go index 6fbad51..394a4c1 100644 --- a/diff_test.go +++ b/diff_test.go @@ -2,6 +2,9 @@ package git import ( "errors" + "fmt" + "io/ioutil" + "path" "strings" "testing" ) @@ -236,3 +239,307 @@ func TestDiffBlobs(t *testing.T) { 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 +} diff --git a/script/build-libgit2.sh b/script/build-libgit2.sh index 7398cd9..98ff78b 100755 --- a/script/build-libgit2.sh +++ b/script/build-libgit2.sh @@ -6,33 +6,54 @@ set -e -if [ "$#" -eq "0" ]; then - echo "Usage: $0 <--dynamic|--static>">&2 +usage() { + echo "Usage: $0 <--dynamic|--static> [--system]">&2 exit 1 +} + +if [ "$#" -eq "0" ]; then + usage fi ROOT="$(cd "$(dirname "$0")/.." && echo "${PWD}")" VENDORED_PATH="${ROOT}/vendor/libgit2" +BUILD_SYSTEM=OFF -case "$1" in - --static) - BUILD_PATH="${ROOT}/static-build" - BUILD_SHARED_LIBS=OFF - ;; +while [ $# -gt 0 ]; do + case "$1" in + --static) + BUILD_PATH="${ROOT}/static-build" + BUILD_SHARED_LIBS=OFF + ;; - --dynamic) - BUILD_PATH="${ROOT}/dynamic-build" - BUILD_SHARED_LIBS=ON - ;; + --dynamic) + BUILD_PATH="${ROOT}/dynamic-build" + BUILD_SHARED_LIBS=ON + ;; - *) - echo "Usage: $0 <--dynamic|--static>">&2 - exit 1 - ;; -esac + --system) + BUILD_SYSTEM=ON + ;; -mkdir -p "${BUILD_PATH}/build" "${BUILD_PATH}/install/lib" + *) + usage + ;; + esac + shift +done +if [ -z "${BUILD_SHARED_LIBS}" ]; then + usage +fi + +if [ "${BUILD_SYSTEM}" = "ON" ]; then + BUILD_INSTALL_PREFIX="/usr" +else + BUILD_INSTALL_PREFIX="${BUILD_PATH}/install" + mkdir -p "${BUILD_PATH}/install/lib" +fi + +mkdir -p "${BUILD_PATH}/build" && cd "${BUILD_PATH}/build" && cmake -DTHREADSAFE=ON \ -DBUILD_CLAR=OFF \ @@ -40,7 +61,7 @@ cmake -DTHREADSAFE=ON \ -DREGEX_BACKEND=builtin \ -DCMAKE_C_FLAGS=-fPIC \ -DCMAKE_BUILD_TYPE="RelWithDebInfo" \ - -DCMAKE_INSTALL_PREFIX="${BUILD_PATH}/install" \ + -DCMAKE_INSTALL_PREFIX="${BUILD_INSTALL_PREFIX}" \ -DCMAKE_INSTALL_LIBDIR="lib" \ "${VENDORED_PATH}" && diff --git a/status.go b/status.go index e37a96d..3923e1a 100644 --- a/status.go +++ b/status.go @@ -6,6 +6,7 @@ package git import "C" import ( + "errors" "runtime" "unsafe" ) @@ -86,6 +87,9 @@ func (statusList *StatusList) ByIndex(index int) (StatusEntry, error) { return StatusEntry{}, ErrInvalid } ptr := C.git_status_byindex(statusList.ptr, C.size_t(index)) + if ptr == nil { + return StatusEntry{}, errors.New("index out of Bounds") + } entry := statusEntryFromC(ptr) runtime.KeepAlive(statusList) diff --git a/status_test.go b/status_test.go index 17ed94f..d5cd530 100644 --- a/status_test.go +++ b/status_test.go @@ -61,3 +61,31 @@ func TestStatusList(t *testing.T) { t.Fatal("Incorrect entry path: ", entry.IndexToWorkdir.NewFile.Path) } } + +func TestStatusNothing(t *testing.T) { + t.Parallel() + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + seedTestRepo(t, repo) + + opts := &StatusOptions{ + Show: StatusShowIndexAndWorkdir, + Flags: StatusOptIncludeUntracked | StatusOptRenamesHeadToIndex | StatusOptSortCaseSensitively, + } + + statusList, err := repo.StatusList(opts) + checkFatal(t, err) + + entryCount, err := statusList.EntryCount() + checkFatal(t, err) + + if entryCount != 0 { + t.Fatal("expected no statuses in empty repo") + } + + _, err = statusList.ByIndex(0) + if err == nil { + t.Error("expected error getting status by index") + } +} diff --git a/wrapper.c b/wrapper.c index ddb4126..0b0f3b4 100644 --- a/wrapper.c +++ b/wrapper.c @@ -6,6 +6,12 @@ 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_buf_fill_null(git_buf *buf) { memset(buf->ptr, '\0', buf->asize*sizeof(char));