diff options
| -rw-r--r-- | .github/workflows/backport.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/ci.yml | 3 | ||||
| -rw-r--r-- | Build_bundled_static.go | 5 | ||||
| -rw-r--r-- | Build_system_dynamic.go | 5 | ||||
| -rw-r--r-- | Build_system_static.go | 5 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | commit.go | 23 | ||||
| -rw-r--r-- | deprecated.go | 16 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | rebase.go | 145 | ||||
| -rw-r--r-- | reference.go | 14 | ||||
| -rw-r--r-- | reference_test.go | 10 | ||||
| -rw-r--r-- | remote.go | 14 | ||||
| m--------- | vendor/libgit2 | 0 | ||||
| -rw-r--r-- | wrapper.c | 28 |
15 files changed, 210 insertions, 77 deletions
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 17f6ed4..9f18fac 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - branch: [ 'release-1.0', 'release-0.28', 'release-0.27' ] + branch: [ 'release-1.1', 'release-1.0', 'release-0.28', 'release-0.27' ] runs-on: ubuntu-20.04 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5d7034..50e5e14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,8 +62,7 @@ jobs: fail-fast: false matrix: libgit2: - - 'v1.1.0' - - 'v1.2.0' + - '109b4c887ffb63962c7017a66fc4a1f48becb48e' # v1.2.0 with a fixed symbol name: Go (system-wide, dynamic) runs-on: ubuntu-20.04 diff --git a/Build_bundled_static.go b/Build_bundled_static.go index 85ed739..09ed0f5 100644 --- a/Build_bundled_static.go +++ b/Build_bundled_static.go @@ -1,3 +1,4 @@ +//go:build static && !system_libgit2 // +build static,!system_libgit2 package git @@ -9,8 +10,8 @@ package git #cgo CFLAGS: -DLIBGIT2_STATIC #include <git2.h> -#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 1 || LIBGIT2_VER_MINOR > 2 -# error "Invalid libgit2 version; this git2go supports libgit2 between v1.1.0 and v1.2.0" +#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2 +# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0" #endif */ import "C" diff --git a/Build_system_dynamic.go b/Build_system_dynamic.go index 348cdc8..9500188 100644 --- a/Build_system_dynamic.go +++ b/Build_system_dynamic.go @@ -1,3 +1,4 @@ +//go:build !static // +build !static package git @@ -7,8 +8,8 @@ package git #cgo CFLAGS: -DLIBGIT2_DYNAMIC #include <git2.h> -#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 1 || LIBGIT2_VER_MINOR > 2 -# error "Invalid libgit2 version; this git2go supports libgit2 between v1.1.0 and v1.2.0" +#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2 +# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0" #endif */ import "C" diff --git a/Build_system_static.go b/Build_system_static.go index c1a8292..309369d 100644 --- a/Build_system_static.go +++ b/Build_system_static.go @@ -1,3 +1,4 @@ +//go:build static && system_libgit2 // +build static,system_libgit2 package git @@ -7,8 +8,8 @@ package git #cgo CFLAGS: -DLIBGIT2_STATIC #include <git2.h> -#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 1 || LIBGIT2_VER_MINOR > 2 -# error "Invalid libgit2 version; this git2go supports libgit2 between v1.1.0 and v1.2.0" +#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2 +# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0" #endif */ import "C" @@ -10,20 +10,21 @@ Due to the fact that Go 1.11 module versions have semantic meaning and don't nec | libgit2 | git2go | |---------|---------------| -| main | (will be v32) | +| main | (will be v33) | +| 1.2 | v32 | | 1.1 | v31 | | 1.0 | v30 | | 0.99 | v29 | | 0.28 | v28 | | 0.27 | v27 | -You can import them in your project with the version's major number as a suffix. For example, if you have libgit2 v1.1 installed, you'd import git2go v31 with: +You can import them in your project with the version's major number as a suffix. For example, if you have libgit2 v1.2 installed, you'd import git2go v32 with: ```sh -go get github.com/libgit2/git2go/v31 +go get github.com/libgit2/git2go/v32 ``` ```go -import "github.com/libgit2/git2go/v31" +import "github.com/libgit2/git2go/v32" ``` which will ensure there are no sudden changes to the API. @@ -44,10 +45,10 @@ This project wraps the functionality provided by libgit2. If you're using a vers ### Versioned branch, dynamic linking -When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via Go modules, e.g. to work against libgit2 v1.1 +When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via Go modules, e.g. to work against libgit2 v1.2 ```go -import "github.com/libgit2/git2go/v31" +import "github.com/libgit2/git2go/v32" ``` ### Versioned branch, static linking @@ -77,7 +78,7 @@ In order to let Go pass the correct flags to `pkg-config`, `-tags static` needs One thing to take into account is that since Go expects the `pkg-config` file to be within the same directory where `make install-static` was called, so the `go.mod` file may need to have a [`replace` directive](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) so that the correct setup is achieved. So if `git2go` is checked out at `$GOPATH/src/github.com/libgit2/git2go` and your project at `$GOPATH/src/github.com/my/project`, the `go.mod` file of `github.com/my/project` might need to have a line like - replace github.com/libgit2/git2go/v31 ../../libgit2/git2go + replace github.com/libgit2/git2go/v32 ../../libgit2/git2go Parallelism and network operations ---------------------------------- @@ -37,10 +37,14 @@ func (c *Commit) Message() string { return ret } -func (c *Commit) MessageEncoding() string { - ret := C.GoString(C.git_commit_message_encoding(c.cast_ptr)) +func (c *Commit) MessageEncoding() MessageEncoding { + ptr := C.git_commit_message_encoding(c.cast_ptr) + if ptr == nil { + return MessageEncodingUTF8 + } + ret := C.GoString(ptr) runtime.KeepAlive(c) - return ret + return MessageEncoding(ret) } func (c *Commit) RawMessage() string { @@ -64,6 +68,19 @@ func (c *Commit) ContentToSign() string { // CommitSigningCallback defines a function type that takes some data to sign and returns (signature, signature_field, error) type CommitSigningCallback func(string) (signature, signatureField string, err error) +// CommitCreateCallback defines a function type that is called when another +// function is going to create commits (for example, Rebase) to allow callers +// to override the commit creation behavior. For example, users may wish to +// sign commits by providing this information to Repository.CreateCommitBuffer, +// signing that buffer, then calling Repository.CreateCommitWithSignature. +type CommitCreateCallback func( + author, committer *Signature, + messageEncoding MessageEncoding, + message string, + tree *Tree, + parents ...*Commit, +) (oid *Oid, err error) + // WithSignatureUsing creates a new signed commit from this one using the given signing callback func (c *Commit) WithSignatureUsing(f CommitSigningCallback) (*Oid, error) { signature, signatureField, err := f(c.ContentToSign()) diff --git a/deprecated.go b/deprecated.go index 5e69a51..01253ee 100644 --- a/deprecated.go +++ b/deprecated.go @@ -237,6 +237,22 @@ func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) return C.int(ErrorCodeOK) } +// reference.go + +// Deprecated: ReferenceIsValidName is a deprecated alias of ReferenceNameIsValid. +func ReferenceIsValidName(name string) bool { + valid, _ := ReferenceNameIsValid(name) + return valid +} + +// remote.go + +// Deprecated: RemoteIsValidName is a deprecated alias of RemoteNameIsValid. +func RemoteIsValidName(name string) bool { + valid, _ := RemoteNameIsValid(name) + return valid +} + // tree.go // Deprecated: CallbackGitTreeWalk is not used. @@ -1,4 +1,4 @@ -module github.com/libgit2/git2go/v31 +module github.com/libgit2/git2go/v32 go 1.13 @@ -9,6 +9,7 @@ import "C" import ( "errors" "fmt" + "reflect" "runtime" "unsafe" ) @@ -71,75 +72,140 @@ func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation { return operation } -//export commitSigningCallback -func commitSigningCallback(errorMessage **C.char, _signature *C.git_buf, _signature_field *C.git_buf, _commit_content *C.char, handle unsafe.Pointer) C.int { +//export commitCreateCallback +func commitCreateCallback( + errorMessage **C.char, + _out *C.git_oid, + _author, _committer *C.git_signature, + _message_encoding, _message *C.char, + _tree *C.git_tree, + _parent_count C.size_t, + _parents **C.git_commit, + handle unsafe.Pointer, +) C.int { data, ok := pointerHandles.Get(handle).(*rebaseOptionsData) if !ok { panic("invalid sign payload") } - if data.options.CommitSigningCallback == nil { + if data.options.CommitCreateCallback == nil && data.options.CommitSigningCallback == nil { return C.int(ErrorCodePassthrough) } - commitContent := C.GoString(_commit_content) + messageEncoding := MessageEncodingUTF8 + if _message_encoding != nil { + messageEncoding = MessageEncoding(C.GoString(_message_encoding)) + } + tree := &Tree{ + Object: Object{ + ptr: (*C.git_object)(_tree), + repo: data.repo, + }, + cast_ptr: _tree, + } - signature, signatureField, err := data.options.CommitSigningCallback(commitContent) - if err != nil { - if data.errorTarget != nil { - *data.errorTarget = err + var goParents []*C.git_commit + if _parent_count > 0 { + hdr := reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(_parents)), + Len: int(_parent_count), + Cap: int(_parent_count), } - return setCallbackError(errorMessage, err) + goParents = *(*[]*C.git_commit)(unsafe.Pointer(&hdr)) } - fillBuf := func(bufData string, buf *C.git_buf) error { - clen := C.size_t(len(bufData)) - cstr := unsafe.Pointer(C.CString(bufData)) - defer C.free(cstr) - - // libgit2 requires the contents of the buffer to be NULL-terminated. - // C.CString() guarantees that the returned buffer will be - // NULL-terminated, so we can safely copy the terminator. - if int(C.git_buf_set(buf, cstr, clen+1)) != 0 { - return errors.New("could not set buffer") + parents := make([]*Commit, int(_parent_count)) + for i, p := range goParents { + parents[i] = &Commit{ + Object: Object{ + ptr: (*C.git_object)(p), + repo: data.repo, + }, + cast_ptr: p, } - - return nil } - if signatureField != "" { - err := fillBuf(signatureField, _signature_field) + if data.options.CommitCreateCallback != nil { + oid, err := data.options.CommitCreateCallback( + newSignatureFromC(_author), + newSignatureFromC(_committer), + messageEncoding, + C.GoString(_message), + tree, + parents..., + ) + if err != nil { + if data.errorTarget != nil { + *data.errorTarget = err + } + return setCallbackError(errorMessage, err) + } + if oid == nil { + return C.int(ErrorCodePassthrough) + } + *_out = *oid.toC() + } else if data.options.CommitSigningCallback != nil { + commitContent, err := data.repo.CreateCommitBuffer( + newSignatureFromC(_author), + newSignatureFromC(_committer), + messageEncoding, + C.GoString(_message), + tree, + parents..., + ) if err != nil { if data.errorTarget != nil { *data.errorTarget = err } return setCallbackError(errorMessage, err) } - } - err = fillBuf(signature, _signature) - if err != nil { - if data.errorTarget != nil { - *data.errorTarget = err + signature, signatureField, err := data.options.CommitSigningCallback(string(commitContent)) + if err != nil { + if data.errorTarget != nil { + *data.errorTarget = err + } + return setCallbackError(errorMessage, err) + } + + oid, err := data.repo.CreateCommitWithSignature(string(commitContent), signature, signatureField) + if err != nil { + if data.errorTarget != nil { + *data.errorTarget = err + } + return setCallbackError(errorMessage, err) } - return setCallbackError(errorMessage, err) + *_out = *oid.toC() } return C.int(ErrorCodeOK) } -// RebaseOptions are used to tell the rebase machinery how to operate +// RebaseOptions are used to tell the rebase machinery how to operate. type RebaseOptions struct { - Quiet int - InMemory int - RewriteNotesRef string - MergeOptions MergeOptions - CheckoutOptions CheckoutOptions + Quiet int + InMemory int + RewriteNotesRef string + MergeOptions MergeOptions + CheckoutOptions CheckoutOptions + // CommitCreateCallback is an optional callback that allows users to override + // commit creation when rebasing. If specified, users can create + // their own commit and provide the commit ID, which may be useful for + // signing commits or otherwise customizing the commit creation. If this + // callback returns a nil Oid, then the rebase will continue to create the + // commit. + CommitCreateCallback CommitCreateCallback + // Deprecated: CommitSigningCallback is an optional callback that will be + // called with the commit content, allowing a signature to be added to the + // rebase commit. This field is only used when rebasing. This callback is + // not invoked if a CommitCreateCallback is specified. CommitCreateCallback + // should be used instead of this. CommitSigningCallback CommitSigningCallback } type rebaseOptionsData struct { options *RebaseOptions + repo *Repository errorTarget *error } @@ -167,7 +233,7 @@ func rebaseOptionsFromC(opts *C.git_rebase_options) RebaseOptions { } } -func populateRebaseOptions(copts *C.git_rebase_options, opts *RebaseOptions, errorTarget *error) *C.git_rebase_options { +func populateRebaseOptions(copts *C.git_rebase_options, opts *RebaseOptions, repo *Repository, errorTarget *error) *C.git_rebase_options { C.git_rebase_options_init(copts, C.GIT_REBASE_OPTIONS_VERSION) if opts == nil { return nil @@ -179,9 +245,10 @@ func populateRebaseOptions(copts *C.git_rebase_options, opts *RebaseOptions, err populateMergeOptions(&copts.merge_options, &opts.MergeOptions) populateCheckoutOptions(&copts.checkout_options, &opts.CheckoutOptions, errorTarget) - if opts.CommitSigningCallback != nil { + if opts.CommitCreateCallback != nil || opts.CommitSigningCallback != nil { data := &rebaseOptionsData{ options: opts, + repo: repo, errorTarget: errorTarget, } C._go_git_populate_rebase_callbacks(copts) @@ -237,7 +304,7 @@ func (r *Repository) InitRebase(branch *AnnotatedCommit, upstream *AnnotatedComm var ptr *C.git_rebase var err error - cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, &err) + cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, r, &err) ret := C.git_rebase_init(&ptr, r.ptr, branch.ptr, upstream.ptr, onto.ptr, cOpts) runtime.KeepAlive(branch) runtime.KeepAlive(upstream) @@ -261,7 +328,7 @@ func (r *Repository) OpenRebase(opts *RebaseOptions) (*Rebase, error) { var ptr *C.git_rebase var err error - cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, &err) + cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, r, &err) ret := C.git_rebase_open(&ptr, r.ptr, cOpts) runtime.KeepAlive(r) if ret == C.int(ErrorCodeUser) && err != nil { diff --git a/reference.go b/reference.go index ae49c21..46e9b48 100644 --- a/reference.go +++ b/reference.go @@ -476,7 +476,7 @@ func (v *ReferenceIterator) Free() { C.git_reference_iterator_free(v.ptr) } -// ReferenceIsValidName returns whether the reference name is well-formed. +// ReferenceNameIsValid returns whether the reference name is well-formed. // // Valid reference names must follow one of two patterns: // @@ -486,11 +486,19 @@ func (v *ReferenceIterator) Free() { // 2. Names prefixed with "refs/" can be almost anything. You must avoid // the characters '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences // ".." and " @ {" which have special meaning to revparse. -func ReferenceIsValidName(name string) bool { +func ReferenceNameIsValid(name string) (bool, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - return C.git_reference_is_valid_name(cname) == 1 + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var valid C.int + ret := C.git_reference_name_is_valid(&valid, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + return valid == 1, nil } const ( diff --git a/reference_test.go b/reference_test.go index 285adb5..46c9e7f 100644 --- a/reference_test.go +++ b/reference_test.go @@ -214,12 +214,16 @@ func TestIsNote(t *testing.T) { } } -func TestReferenceIsValidName(t *testing.T) { +func TestReferenceNameIsValid(t *testing.T) { t.Parallel() - if !ReferenceIsValidName("HEAD") { + valid, err := ReferenceNameIsValid("HEAD") + checkFatal(t, err) + if !valid { t.Errorf("HEAD should be a valid reference name") } - if ReferenceIsValidName("HEAD1") { + valid, err = ReferenceNameIsValid("HEAD1") + checkFatal(t, err) + if valid { t.Errorf("HEAD1 should not be a valid reference name") } } @@ -564,12 +564,20 @@ func freeProxyOptions(copts *C.git_proxy_options) { C.free(unsafe.Pointer(copts.url)) } -// RemoteIsValidName returns whether the remote name is well-formed. -func RemoteIsValidName(name string) bool { +// RemoteNameIsValid returns whether the remote name is well-formed. +func RemoteNameIsValid(name string) (bool, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - return C.git_remote_is_valid_name(cname) == 1 + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var valid C.int + ret := C.git_remote_name_is_valid(&valid, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + return valid == 1, nil } // free releases the resources of the Remote. diff --git a/vendor/libgit2 b/vendor/libgit2 -Subproject 7f4fa178629d559c037a1f72f79f79af9c1ef8c +Subproject 109b4c887ffb63962c7017a66fc4a1f48becb48 @@ -116,18 +116,28 @@ void _go_git_populate_apply_callbacks(git_apply_options *options) options->hunk_cb = (git_apply_hunk_cb)&hunkApplyCallback; } -static int commit_signing_callback( - git_buf *signature, - git_buf *signature_field, - const char *commit_contents, +static int commit_create_callback( + git_oid *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[], void *payload) { char *error_message = NULL; - const int ret = commitSigningCallback( + const int ret = commitCreateCallback( &error_message, - signature, - signature_field, - (char *)commit_contents, + out, + (git_signature *)author, + (git_signature *)committer, + (char *)message_encoding, + (char *)message, + (git_tree *)tree, + parent_count, + (git_commit **)parents, payload ); return set_callback_error(error_message, ret); @@ -135,7 +145,7 @@ static int commit_signing_callback( void _go_git_populate_rebase_callbacks(git_rebase_options *opts) { - opts->signing_cb = commit_signing_callback; + opts->commit_create_cb = commit_create_callback; } void _go_git_populate_clone_callbacks(git_clone_options *opts) |
