Compare commits
15 Commits
main
...
cmn/go-htt
Author | SHA1 | Date |
---|---|---|
|
b63157de51 | |
|
6fe4f461f6 | |
|
2d54afed61 | |
|
b2ec6e6bfc | |
|
22da146353 | |
|
50f588fcc4 | |
|
c5e6dc298e | |
|
dd57a6a89b | |
|
2aa2ae5150 | |
|
1339ad1e71 | |
|
e2eda97316 | |
|
464c24ab07 | |
|
e80ae1125a | |
|
c726f932db | |
|
1be680fdf0 |
|
@ -7,6 +7,13 @@ go:
|
|||
|
||||
script: make test-static
|
||||
|
||||
# The default of --recursive chokes on the libgit2 tests
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
before_install:
|
||||
- git submodule update --init
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
|
10
git.go
10
git.go
|
@ -129,6 +129,14 @@ func init() {
|
|||
panic("libgit2 was not built with threading support")
|
||||
}
|
||||
|
||||
if err := RegisterManagedHttp(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := RegisterManagedHttps(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// This is not something we should be doing, as we may be
|
||||
// stomping all over someone else's setup. The user should do
|
||||
// this themselves or use some binding/wrapper which does it
|
||||
|
@ -271,7 +279,7 @@ func MakeGitError(errorCode C.int) error {
|
|||
|
||||
var errMessage string
|
||||
var errClass ErrorClass
|
||||
if errorCode != C.GIT_ITEROVER {
|
||||
if errorCode != C.GIT_ITEROVER && errorCode != C.GIT_EUSER {
|
||||
err := C.giterr_last()
|
||||
if err != nil {
|
||||
errMessage = C.GoString(err.message)
|
||||
|
|
|
@ -0,0 +1,455 @@
|
|||
package git
|
||||
|
||||
/*
|
||||
#include <git2.h>
|
||||
#include <git2/sys/transport.h>
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
void *ptr;
|
||||
} managed_smart_subtransport;
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport_stream parent;
|
||||
void *ptr;
|
||||
} managed_smart_subtransport_stream;
|
||||
|
||||
int _go_git_transport_register(const char *scheme);
|
||||
int _go_git_transport_smart(git_transport **out, git_remote *owner);
|
||||
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr);
|
||||
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *t, void *ptr);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type SmartService int
|
||||
|
||||
const (
|
||||
SmartServiceUploadpackLs = C.GIT_SERVICE_UPLOADPACK_LS
|
||||
SmartServiceUploadpack = C.GIT_SERVICE_UPLOADPACK
|
||||
SmartServiceReceivepackLs = C.GIT_SERVICE_RECEIVEPACK_LS
|
||||
SmartServiceReceivepack = C.GIT_SERVICE_RECEIVEPACK
|
||||
)
|
||||
|
||||
type SmartSubtransport interface {
|
||||
Action(url string, action SmartService) (SmartSubtransportStream, error)
|
||||
Close() error
|
||||
Free()
|
||||
}
|
||||
|
||||
type SmartSubtransportStream interface {
|
||||
Read(buf []byte) (int, error)
|
||||
Write(buf []byte) error
|
||||
Free()
|
||||
}
|
||||
|
||||
func RegisterManagedHttp() error {
|
||||
httpStr := C.CString("http")
|
||||
defer C.free(unsafe.Pointer(httpStr))
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ret := C._go_git_transport_register(httpStr)
|
||||
if ret != 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RegisterManagedHttps() error {
|
||||
httpsStr := C.CString("https")
|
||||
defer C.free(unsafe.Pointer(httpsStr))
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ret := C._go_git_transport_register(httpsStr)
|
||||
if ret != 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ManagedTransport struct {
|
||||
owner *C.git_transport
|
||||
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) Action(url string, action SmartService) (SmartSubtransportStream, error) {
|
||||
if err := self.ensureClient(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
switch action {
|
||||
case SmartServiceUploadpackLs:
|
||||
req, err = http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil)
|
||||
|
||||
case SmartServiceUploadpack:
|
||||
req, err = http.NewRequest("POST", url+"/git-upload-pack", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
req.Header["Content-Type"] = []string{"application/x-git-upload-pack-request"}
|
||||
|
||||
case SmartServiceReceivepackLs:
|
||||
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
|
||||
|
||||
case SmartServiceReceivepack:
|
||||
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
req.Header["Content-Type"] = []string{"application/x-git-receive-pack-request"}
|
||||
default:
|
||||
err = errors.New("unknown action")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header["User-Agent"] = []string{"git/2.0 (git2go)"}
|
||||
|
||||
stream := newManagedHttpStream(self, req)
|
||||
if req.Method == "POST" {
|
||||
stream.recvReply.Add(1)
|
||||
stream.sendRequestBackground()
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) Close() error {
|
||||
self.client = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) Free() {
|
||||
}
|
||||
|
||||
func (self *ManagedTransport) ensureClient() error {
|
||||
if self.client != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
var cpopts C.git_proxy_options
|
||||
if ret := C.git_transport_smart_proxy_options(&cpopts, self.owner); ret < 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
var proxyFn func(*http.Request) (*url.URL, error)
|
||||
proxyOpts := proxyOptionsFromC(&cpopts)
|
||||
switch proxyOpts.Type {
|
||||
case ProxyTypeNone:
|
||||
proxyFn = nil
|
||||
case ProxyTypeAuto:
|
||||
proxyFn = http.ProxyFromEnvironment
|
||||
case ProxyTypeSpecified:
|
||||
parsedUrl, err := url.Parse(proxyOpts.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxyFn = http.ProxyURL(parsedUrl)
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: proxyFn,
|
||||
}
|
||||
self.client = &http.Client{Transport: transport}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ManagedHttpStream struct {
|
||||
owner *ManagedTransport
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
reader *io.PipeReader
|
||||
writer *io.PipeWriter
|
||||
sentRequest bool
|
||||
recvReply sync.WaitGroup
|
||||
httpError error
|
||||
}
|
||||
|
||||
func newManagedHttpStream(owner *ManagedTransport, req *http.Request) *ManagedHttpStream {
|
||||
r, w := io.Pipe()
|
||||
return &ManagedHttpStream{
|
||||
owner: owner,
|
||||
req: req,
|
||||
reader: r,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Read(buf []byte) (int, error) {
|
||||
if !self.sentRequest {
|
||||
self.recvReply.Add(1)
|
||||
if err := self.sendRequest(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.writer.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
self.recvReply.Wait()
|
||||
|
||||
if self.httpError != nil {
|
||||
return 0, self.httpError
|
||||
}
|
||||
|
||||
return self.resp.Body.Read(buf)
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Write(buf []byte) error {
|
||||
if self.httpError != nil {
|
||||
return self.httpError
|
||||
}
|
||||
self.writer.Write(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) Free() {
|
||||
self.resp.Body.Close()
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) sendRequestBackground() {
|
||||
go func() {
|
||||
self.httpError = self.sendRequest()
|
||||
}()
|
||||
self.sentRequest = true
|
||||
}
|
||||
|
||||
func (self *ManagedHttpStream) sendRequest() error {
|
||||
defer self.recvReply.Done()
|
||||
self.resp = nil
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
var userName string
|
||||
var password string
|
||||
for {
|
||||
req := &http.Request{
|
||||
Method: self.req.Method,
|
||||
URL: self.req.URL,
|
||||
Header: self.req.Header,
|
||||
}
|
||||
if req.Method == "POST" {
|
||||
req.Body = self.reader
|
||||
req.ContentLength = -1
|
||||
}
|
||||
|
||||
req.SetBasicAuth(userName, password)
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
break
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
resp.Body.Close()
|
||||
var cred *C.git_cred
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ret := C.git_transport_smart_credentials(&cred, self.owner.owner, nil, C.GIT_CREDTYPE_USERPASS_PLAINTEXT)
|
||||
|
||||
if ret != 0 {
|
||||
return MakeGitError(ret)
|
||||
}
|
||||
|
||||
if cred.credtype != C.GIT_CREDTYPE_USERPASS_PLAINTEXT {
|
||||
C.git_cred_free(cred)
|
||||
return fmt.Errorf("Unexpected credential type %d", cred.credtype)
|
||||
}
|
||||
ptCred := (*C.git_cred_userpass_plaintext)(unsafe.Pointer(cred))
|
||||
userName = C.GoString(ptCred.username)
|
||||
password = C.GoString(ptCred.password)
|
||||
C.git_cred_free(cred)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other error we treat as a hard error and punt back to the caller
|
||||
resp.Body.Close()
|
||||
return fmt.Errorf("Unhandled HTTP error %s", resp.Status)
|
||||
}
|
||||
|
||||
self.sentRequest = true
|
||||
self.resp = resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func setLibgit2Error(err error) C.int {
|
||||
cstr := C.CString(err.Error())
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.giterr_set_str(C.GITERR_NET, cstr)
|
||||
|
||||
if gitErr, ok := err.(*GitError); ok {
|
||||
return C.int(gitErr.Code)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
//export httpAction
|
||||
func httpAction(out **C.git_smart_subtransport_stream, t *C.git_smart_subtransport, url *C.char, action C.git_smart_service_t) C.int {
|
||||
transport, err := getSmartSubtransportInterface(t)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
managed, err := transport.Action(C.GoString(url), SmartService(action))
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
stream := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport_stream{})))
|
||||
managedPtr := pointerHandles.Track(managed)
|
||||
C._go_git_setup_smart_subtransport_stream((*C.managed_smart_subtransport_stream)(stream), managedPtr)
|
||||
|
||||
*out = (*C.git_smart_subtransport_stream)(stream)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export httpClose
|
||||
func httpClose(t *C.git_smart_subtransport) C.int {
|
||||
transport, err := getSmartSubtransportInterface(t)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
if err := transport.Close(); err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export httpFree
|
||||
func httpFree(transport *C.git_smart_subtransport) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(transport))
|
||||
pointerHandles.Untrack(wrapperPtr.ptr)
|
||||
}
|
||||
|
||||
var errNoSmartSubtransport = errors.New("passed object does not implement SmartSubtransport")
|
||||
|
||||
func getSmartSubtransportInterface(_t *C.git_smart_subtransport) (SmartSubtransport, error) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport)(unsafe.Pointer(_t))
|
||||
|
||||
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransport)
|
||||
if !ok {
|
||||
return nil, errNoSmartSubtransport
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
//export httpTransportCb
|
||||
func httpTransportCb(out **C.git_transport, owner *C.git_remote, param unsafe.Pointer) C.int {
|
||||
return C._go_git_transport_smart(out, owner)
|
||||
}
|
||||
|
||||
//export httpSmartSubtransportCb
|
||||
func httpSmartSubtransportCb(out **C.git_smart_subtransport, owner *C.git_transport, param unsafe.Pointer) C.int {
|
||||
if out == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
transport := C.calloc(1, C.size_t(unsafe.Sizeof(C.managed_smart_subtransport{})))
|
||||
managed := &ManagedTransport{owner: owner}
|
||||
managedPtr := pointerHandles.Track(managed)
|
||||
C._go_git_setup_smart_subtransport((*C.managed_smart_subtransport)(transport), managedPtr)
|
||||
|
||||
*out = (*C.git_smart_subtransport)(transport)
|
||||
return 0
|
||||
}
|
||||
|
||||
var errNoSmartSubtransportStream = errors.New("passed object does not implement SmartSubtransportStream")
|
||||
|
||||
func getSmartSubtransportStreamInterface(_s *C.git_smart_subtransport_stream) (SmartSubtransportStream, error) {
|
||||
wrapperPtr := (*C.managed_smart_subtransport_stream)(unsafe.Pointer(_s))
|
||||
|
||||
transport, ok := pointerHandles.Get(wrapperPtr.ptr).(SmartSubtransportStream)
|
||||
if !ok {
|
||||
return nil, errNoSmartSubtransportStream
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
//export smartSubtransportRead
|
||||
func smartSubtransportRead(s *C.git_smart_subtransport_stream, data *C.char, l C.size_t, read *C.size_t) C.int {
|
||||
stream, err := getSmartSubtransportStreamInterface(s)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
var p []byte
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
header.Cap = int(l)
|
||||
header.Len = int(l)
|
||||
header.Data = uintptr(unsafe.Pointer(data))
|
||||
|
||||
n, err := stream.Read(p)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
*read = C.size_t(0)
|
||||
return 0
|
||||
}
|
||||
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
*read = C.size_t(n)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export smartSubtransportWrite
|
||||
func smartSubtransportWrite(s *C.git_smart_subtransport_stream, data unsafe.Pointer, l C.size_t) C.int {
|
||||
stream, err := getSmartSubtransportStreamInterface(s)
|
||||
if err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
var p []byte
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
header.Cap = int(l)
|
||||
header.Len = int(l)
|
||||
header.Data = uintptr(data)
|
||||
|
||||
if err := stream.Write(p); err != nil {
|
||||
return setLibgit2Error(err)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
//export smartSubtransportFree
|
||||
func smartSubtransportFree(s *C.git_smart_subtransport_stream) {
|
||||
}
|
|
@ -141,6 +141,13 @@ type ProxyOptions struct {
|
|||
Url string
|
||||
}
|
||||
|
||||
func proxyOptionsFromC(copts *C.git_proxy_options) ProxyOptions {
|
||||
return ProxyOptions{
|
||||
Type: ProxyType(copts._type),
|
||||
Url: C.GoString(copts.url),
|
||||
}
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
ptr *C.git_remote
|
||||
callbacks RemoteCallbacks
|
||||
|
|
|
@ -184,3 +184,26 @@ func TestRemotePrune(t *testing.T) {
|
|||
t.Fatal("Expected error getting a pruned reference")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteCredentialsCalled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := createTestRepo(t)
|
||||
defer cleanupTestRepo(t, repo)
|
||||
|
||||
remote, err := repo.Remotes.CreateAnonymous("https://github.com/libgit2/non-existent")
|
||||
checkFatal(t, err)
|
||||
|
||||
fetchOpts := FetchOptions{
|
||||
RemoteCallbacks: RemoteCallbacks{
|
||||
CredentialsCallback: func(url, username string, allowedTypes CredType) (ErrorCode, *Cred) {
|
||||
return ErrUser, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = remote.Fetch(nil, &fetchOpts, "fetch")
|
||||
if !IsErrorCode(err, ErrUser) {
|
||||
t.Fatal("Bad Fetch return, expected ErrUser, got", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
type SubmoduleUpdateOptions struct {
|
||||
*CheckoutOpts
|
||||
*FetchOptions
|
||||
CloneCheckoutStrategy CheckoutStrategy
|
||||
}
|
||||
|
||||
// Submodule
|
||||
|
@ -369,7 +368,6 @@ func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *S
|
|||
|
||||
populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
|
||||
populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions)
|
||||
ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit df4dfaadcf709646ebab2e57e3589952cf1ac809
|
||||
Subproject commit a1023a43027207ac7a5df7233bddfe7347bee256
|
38
wrapper.c
38
wrapper.c
|
@ -2,6 +2,7 @@
|
|||
#include <git2.h>
|
||||
#include <git2/sys/odb_backend.h>
|
||||
#include <git2/sys/refdb_backend.h>
|
||||
#include <git2/sys/transport.h>
|
||||
|
||||
typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload);
|
||||
|
||||
|
@ -180,4 +181,41 @@ void _go_git_writestream_free(git_writestream *stream)
|
|||
stream->free(stream);
|
||||
}
|
||||
|
||||
int _go_git_transport_register(const char *scheme)
|
||||
{
|
||||
return git_transport_register(scheme, httpTransportCb, NULL);
|
||||
}
|
||||
|
||||
int _go_git_transport_smart(git_transport **out, git_remote *owner)
|
||||
{
|
||||
git_smart_subtransport_definition defn = {
|
||||
httpSmartSubtransportCb,
|
||||
1, // RPC/Stateless
|
||||
};
|
||||
|
||||
return git_transport_smart(out, owner, &defn);
|
||||
}
|
||||
|
||||
void _go_git_setup_smart_subtransport(managed_smart_subtransport *t, void *ptr)
|
||||
{
|
||||
typedef int (*transport_action)(git_smart_subtransport_stream **out, git_smart_subtransport *transport, const char *url, git_smart_service_t action);
|
||||
|
||||
t->parent.action = (transport_action)httpAction;
|
||||
t->parent.close = httpClose;
|
||||
t->parent.free = httpFree;
|
||||
|
||||
t->ptr = ptr;
|
||||
}
|
||||
|
||||
void _go_git_setup_smart_subtransport_stream(managed_smart_subtransport_stream *s, void *ptr)
|
||||
{
|
||||
typedef int (*transport_stream_write)(git_smart_subtransport_stream *stream, const char *buffer, size_t len);
|
||||
|
||||
s->parent.read = smartSubtransportRead;
|
||||
s->parent.write = (transport_stream_write)smartSubtransportWrite;
|
||||
s->parent.free = smartSubtransportFree;
|
||||
|
||||
s->ptr = ptr;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
|
|
Loading…
Reference in New Issue