Repository: libgit2/git2go (go)

Issue:
git revert
I don't see any go bindings for revert functionality in libgit. Is this intentional or something which is missing and needs to be added? I can create a patch, but wanted to check with contributors if there is a workaround or specific reason not to do it.

Pull request description:
Add revert functionality
Closes #436

Patch:
diff --git a/revert.go b/revert.go
new file mode 100644
index 0000000..e745f11
--- /dev/null
+++ b/revert.go
@@ -0,0 +1,101 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+	"runtime"
+)
+
+// RevertOptions contains options for performing a revert
+type RevertOptions struct {
+	Mainline     uint
+	MergeOpts    MergeOptions
+	CheckoutOpts CheckoutOpts
+}
+
+func (opts *RevertOptions) toC() *C.git_revert_options {
+	return &C.git_revert_options{
+		version:       C.GIT_REVERT_OPTIONS_VERSION,
+		mainline:      C.uint(opts.Mainline),
+		merge_opts:    *opts.MergeOpts.toC(),
+		checkout_opts: *opts.CheckoutOpts.toC(),
+	}
+}
+
+func revertOptionsFromC(opts *C.git_revert_options) RevertOptions {
+	return RevertOptions{
+		Mainline:     uint(opts.mainline),
+		MergeOpts:    mergeOptionsFromC(&opts.merge_opts),
+		CheckoutOpts: checkoutOptionsFromC(&opts.checkout_opts),
+	}
+}
+
+func freeRevertOptions(opts *C.git_revert_options) {
+	freeCheckoutOpts(&opts.checkout_opts)
+}
+
+// DefaultRevertOptions initialises a RevertOptions struct with default values
+func DefaultRevertOptions() (RevertOptions, error) {
+	opts := C.git_revert_options{}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	ecode := C.git_revert_init_options(&opts, C.GIT_REVERT_OPTIONS_VERSION)
+	if ecode < 0 {
+		return RevertOptions{}, MakeGitError(ecode)
+	}
+
+	defer freeRevertOptions(&opts)
+	return revertOptionsFromC(&opts), nil
+}
+
+// Revert the provided commit leaving the index updated with the results of the revert
+func (r *Repository) Revert(commit *Commit, revertOptions *RevertOptions) error {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var cOpts *C.git_revert_options
+
+	if revertOptions != nil {
+		cOpts = revertOptions.toC()
+		defer freeRevertOptions(cOpts)
+	}
+
+	ecode := C.git_revert(r.ptr, commit.cast_ptr, cOpts)
+	runtime.KeepAlive(r)
+	runtime.KeepAlive(commit)
+
+	if ecode < 0 {
+		return MakeGitError(ecode)
+	}
+
+	return nil
+}
+
+// RevertCommit reverts the provided commit against "ourCommit"
+// The returned index contains the result of the revert and should be freed
+func (r *Repository) RevertCommit(revertCommit *Commit, ourCommit *Commit, mainline uint, mergeOptions *MergeOptions) (*Index, error) {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	var cOpts *C.git_merge_options
+
+	if mergeOptions != nil {
+		cOpts = mergeOptions.toC()
+	}
+
+	var index *C.git_index
+
+	ecode := C.git_revert_commit(&index, r.ptr, revertCommit.cast_ptr, ourCommit.cast_ptr, C.uint(mainline), cOpts)
+	runtime.KeepAlive(revertCommit)
+	runtime.KeepAlive(ourCommit)
+
+	if ecode < 0 {
+		return nil, MakeGitError(ecode)
+	}
+
+	return newIndexFromC(index, r), nil
+}


Test patch:
diff --git a/revert_test.go b/revert_test.go
new file mode 100644
index 0000000..fcf8e43
--- /dev/null
+++ b/revert_test.go
@@ -0,0 +1,76 @@
+package git
+
+import (
+	"testing"
+)
+
+const (
+	expectedRevertedReadmeContents = "foo\n"
+)
+
+func TestRevert(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	seedTestRepo(t, repo)
+	commitID, _ := updateReadme(t, repo, content)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	revertOptions, err := DefaultRevertOptions()
+	checkFatal(t, err)
+
+	err = repo.Revert(commit, &revertOptions)
+	checkFatal(t, err)
+
+	actualReadmeContents := readReadme(t, repo)
+
+	if actualReadmeContents != expectedRevertedReadmeContents {
+		t.Fatalf(`README has incorrect contents after revert. Expected: "%v", Actual: "%v"`,
+			expectedRevertedReadmeContents, actualReadmeContents)
+	}
+
+	state := repo.State()
+	if state != RepositoryStateRevert {
+		t.Fatalf("Incorrect repository state. Expected: %v, Actual: %v", RepositoryStateRevert, state)
+	}
+
+	err = repo.StateCleanup()
+	checkFatal(t, err)
+
+	state = repo.State()
+	if state != RepositoryStateNone {
+		t.Fatalf("Incorrect repository state. Expected: %v, Actual: %v", RepositoryStateNone, state)
+	}
+}
+
+func TestRevertCommit(t *testing.T) {
+	t.Parallel()
+	repo := createTestRepo(t)
+	defer cleanupTestRepo(t, repo)
+
+	seedTestRepo(t, repo)
+	commitID, _ := updateReadme(t, repo, content)
+
+	commit, err := repo.LookupCommit(commitID)
+	checkFatal(t, err)
+
+	revertOptions, err := DefaultRevertOptions()
+	checkFatal(t, err)
+
+	index, err := repo.RevertCommit(commit, commit, 0, &revertOptions.MergeOpts)
+	checkFatal(t, err)
+	defer index.Free()
+
+	err = repo.CheckoutIndex(index, &revertOptions.CheckoutOpts)
+	checkFatal(t, err)
+
+	actualReadmeContents := readReadme(t, repo)
+
+	if actualReadmeContents != expectedRevertedReadmeContents {
+		t.Fatalf(`README has incorrect contents after revert. Expected: "%v", Actual: "%v"`,
+			expectedRevertedReadmeContents, actualReadmeContents)
+	}
+}