diff options
author | ferhat elmas <elmas.ferhat@gmail.com> | 2016-11-15 01:18:53 +0100 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2016-11-15 01:18:53 +0100 |
commit | 16d86605732ba3198c0acd4317b53cf4991a7d4d (patch) | |
tree | 3306f0438235f7dfe19fd37c5393a4794abe0535 | |
parent | eb89d2dd9a36440d58aea224c055b364e49785f7 (diff) | |
download | go-git-16d86605732ba3198c0acd4317b53cf4991a7d4d.tar.gz |
Add configurable http client factory (fixes #120) (#121)
* new http client factory ready to install/override default http(s)
* mv GitUploadPackServiceFactory to clients.common pkg
* rename http.HTTPError to http.Err
* rename http.HTTPAuthMethod to http.AuthMethod
* add doc and examples/ usage
* general improvements:
- update install link in readme to v4 (example are already pointing v4)
- fix indentation in package doc (styling for godoc.org)
- use http.Status constants instead of integers
- close leaked response body
- rm named returns which stutter in doc
- fix one format string
- rm unnecessary if checks
- documentation fixes
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | doc.go | 49 | ||||
-rw-r--r-- | examples/custom_http_client/main.go | 59 | ||||
-rw-r--r-- | plumbing/client/common.go | 8 | ||||
-rw-r--r-- | plumbing/client/common/common.go | 3 | ||||
-rw-r--r-- | plumbing/client/http/common.go | 27 | ||||
-rw-r--r-- | plumbing/client/http/common_test.go | 19 | ||||
-rw-r--r-- | plumbing/client/http/git_upload_pack.go | 32 | ||||
-rw-r--r-- | plumbing/client/http/git_upload_pack_test.go | 14 | ||||
-rw-r--r-- | plumbing/client/ssh/git_upload_pack.go | 16 | ||||
-rw-r--r-- | remote.go | 7 | ||||
-rw-r--r-- | remote_test.go | 46 |
12 files changed, 205 insertions, 77 deletions
@@ -30,7 +30,7 @@ Installation The recommended way to install *go-git* is: ``` -go get -u gopkg.in/src-d/go-git.v3/... +go get -u gopkg.in/src-d/go-git.v4/... ``` @@ -6,31 +6,32 @@ // extensions. // // Small example extracting the commits from a repository: -// func ExampleBasic_printCommits() { -// r := git.NewMemoryRepository() -// o := &git.CloneOptions{ -// URL: "https://github.com/src-d/go-git", -// } -// if err := r.Clone(o); err != nil { -// panic(err) -// } // -// iter, err := r.Commits() -// if err != nil { -// panic(err) -// } -// defer iter.Close() +// func ExampleBasic_printCommits() { +// r := git.NewMemoryRepository() +// o := &git.CloneOptions{ +// URL: "https://github.com/src-d/go-git", +// } +// if err := r.Clone(o); err != nil { +// panic(err) +// } // -// for { -// commit, err := iter.Next() -// if err != nil { -// if err == io.EOF { -// break -// } -// panic(err) -// } +// iter, err := r.Commits() +// if err != nil { +// panic(err) +// } +// defer iter.Close() // -// fmt.Println(commit) -// } -// } +// for { +// commit, err := iter.Next() +// if err != nil { +// if err == io.EOF { +// break +// } +// panic(err) +// } +// +// fmt.Println(commit) +// } +// } package git // import "gopkg.in/src-d/go-git.v4" diff --git a/examples/custom_http_client/main.go b/examples/custom_http_client/main.go new file mode 100644 index 0000000..f28590f --- /dev/null +++ b/examples/custom_http_client/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "crypto/tls" + "net/http" + "time" + + "github.com/fatih/color" + + git "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing/client" + githttp "gopkg.in/src-d/go-git.v4/plumbing/client/http" +) + +func main() { + // Create a custom http(s) client + customClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, // accept any certificate (might be useful for testing) + Timeout: 15 * time.Second, // 15 second timeout + CheckRedirect: func(req *http.Request, via []*http.Request) error { // don't follow redirect + return http.ErrUseLastResponse + }, + } + // Override http(s) default protocol to use our custom client + clients.InstallProtocol( + "https", + githttp.NewGitUploadPackServiceFactory(customClient)) + + // Create an in-memory repository + r := git.NewMemoryRepository() + + const url = "https://github.com/git-fixtures/basic.git" + + // Clone repo + if err := r.Clone(&git.CloneOptions{URL: url}); err != nil { + panic(err) + } + + // Retrieve the branch pointed by HEAD + head, err := r.Head() + if err != nil { + panic(err) + } + + // Print latest commit + commit, err := r.Commit(head.Hash()) + if err != nil { + panic(err) + } + color.Green(commit.String()) + // Output: + // commit 6ecf0ef2c2dffb796033e5a02219af86ec6584e5 + // Author: Máximo Cuadros Ortiz <mcuadros@gmail.com> + // Date: Sun Apr 05 23:30:47 2015 +0200 + // + // vendor stuff +} diff --git a/plumbing/client/common.go b/plumbing/client/common.go index 6a99339..1524753 100644 --- a/plumbing/client/common.go +++ b/plumbing/client/common.go @@ -1,4 +1,4 @@ -// Package clients includes the implementation for diferent transport protocols +// Package clients includes the implementation for different transport protocols // // go-git needs the packfile and the refs of the repo. The // `NewGitUploadPackService` function returns an object that allows to @@ -21,17 +21,15 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/client/ssh" ) -type GitUploadPackServiceFactory func(common.Endpoint) common.GitUploadPackService - // Protocols are the protocols supported by default. -var Protocols = map[string]GitUploadPackServiceFactory{ +var Protocols = map[string]common.GitUploadPackServiceFactory{ "http": http.NewGitUploadPackService, "https": http.NewGitUploadPackService, "ssh": ssh.NewGitUploadPackService, } // InstallProtocol adds or modifies an existing protocol. -func InstallProtocol(scheme string, f GitUploadPackServiceFactory) { +func InstallProtocol(scheme string, f common.GitUploadPackServiceFactory) { Protocols[scheme] = f } diff --git a/plumbing/client/common/common.go b/plumbing/client/common/common.go index 97f78c4..b2d52e8 100644 --- a/plumbing/client/common/common.go +++ b/plumbing/client/common/common.go @@ -36,6 +36,9 @@ type GitUploadPackService interface { Disconnect() error } +// GitUploadPackServiceFactory is capable of instantiating GitUploadPackService with given endpoint +type GitUploadPackServiceFactory func(Endpoint) GitUploadPackService + type AuthMethod interface { Name() string String() string diff --git a/plumbing/client/http/common.go b/plumbing/client/http/common.go index 4c07876..2447995 100644 --- a/plumbing/client/http/common.go +++ b/plumbing/client/http/common.go @@ -9,8 +9,8 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/client/common" ) -// HTTPAuthMethod concrete implementation of common.AuthMethod for HTTP services -type HTTPAuthMethod interface { +// AuthMethod is concrete implementation of common.AuthMethod for HTTP services +type AuthMethod interface { common.AuthMethod setAuth(r *http.Request) } @@ -29,7 +29,7 @@ func (a *BasicAuth) setAuth(r *http.Request) { r.SetBasicAuth(a.username, a.password) } -// Name name of the auth +// Name is name of the auth func (a *BasicAuth) Name() string { return "http-basic-auth" } @@ -43,34 +43,33 @@ func (a *BasicAuth) String() string { return fmt.Sprintf("%s - %s:%s", a.Name(), a.username, masked) } -// HTTPError a dedicated error to return errors bases on status codes -type HTTPError struct { +// Err is a dedicated error to return errors based on status code +type Err struct { Response *http.Response } -// NewHTTPError returns a new HTTPError based on a http response -func NewHTTPError(r *http.Response) error { - if r.StatusCode >= 200 && r.StatusCode < 300 { +// NewErr returns a new Err based on a http response +func NewErr(r *http.Response) error { + if r.StatusCode >= http.StatusOK && r.StatusCode < http.StatusMultipleChoices { return nil } switch r.StatusCode { - case 401: + case http.StatusUnauthorized: return common.ErrAuthorizationRequired - case 404: + case http.StatusNotFound: return common.ErrRepositoryNotFound } - err := &HTTPError{r} - return plumbing.NewUnexpectedError(err) + return plumbing.NewUnexpectedError(&Err{r}) } // StatusCode returns the status code of the response -func (e *HTTPError) StatusCode() int { +func (e *Err) StatusCode() int { return e.Response.StatusCode } -func (e *HTTPError) Error() string { +func (e *Err) Error() string { return fmt.Sprintf("unexpected requesting %q status code: %d", e.Response.Request.URL, e.Response.StatusCode, ) diff --git a/plumbing/client/http/common_test.go b/plumbing/client/http/common_test.go index 287897d..7503d84 100644 --- a/plumbing/client/http/common_test.go +++ b/plumbing/client/http/common_test.go @@ -20,23 +20,22 @@ func (s *SuiteCommon) TestNewBasicAuth(c *C) { c.Assert(a.String(), Equals, "http-basic-auth - foo:*******") } -func (s *SuiteCommon) TestNewHTTPError200(c *C) { - res := &http.Response{StatusCode: 200} - res.StatusCode = 200 - err := NewHTTPError(res) +func (s *SuiteCommon) TestNewErrOK(c *C) { + res := &http.Response{StatusCode: http.StatusOK} + err := NewErr(res) c.Assert(err, IsNil) } -func (s *SuiteCommon) TestNewHTTPError401(c *C) { - s.testNewHTTPError(c, 401, "authorization required") +func (s *SuiteCommon) TestNewErrUnauthorized(c *C) { + s.testNewHTTPError(c, http.StatusUnauthorized, "authorization required") } -func (s *SuiteCommon) TestNewHTTPError404(c *C) { - s.testNewHTTPError(c, 404, "repository not found") +func (s *SuiteCommon) TestNewErrNotFound(c *C) { + s.testNewHTTPError(c, http.StatusNotFound, "repository not found") } func (s *SuiteCommon) TestNewHTTPError40x(c *C) { - s.testNewHTTPError(c, 402, "unexpected client error.*") + s.testNewHTTPError(c, http.StatusPaymentRequired, "unexpected client error.*") } func (s *SuiteCommon) testNewHTTPError(c *C, code int, msg string) { @@ -46,7 +45,7 @@ func (s *SuiteCommon) testNewHTTPError(c *C, code int, msg string) { Request: req, } - err := NewHTTPError(res) + err := NewErr(res) c.Assert(err, NotNil) c.Assert(err, ErrorMatches, msg) } diff --git a/plumbing/client/http/git_upload_pack.go b/plumbing/client/http/git_upload_pack.go index c1f4a0b..1ecf299 100644 --- a/plumbing/client/http/git_upload_pack.go +++ b/plumbing/client/http/git_upload_pack.go @@ -13,26 +13,41 @@ import ( "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline" ) -// GitUploadPackService git-upoad-pack service over HTTP +// GitUploadPackService git-upload-pack service over HTTP type GitUploadPackService struct { client *http.Client endpoint common.Endpoint - auth HTTPAuthMethod + auth AuthMethod } // NewGitUploadPackService connects to a git-upload-pack service over HTTP, the // auth is extracted from the URL, or can be provided using the SetAuth method func NewGitUploadPackService(endpoint common.Endpoint) common.GitUploadPackService { + return newGitUploadPackService(endpoint, http.DefaultClient) +} + +// NewGitUploadPackServiceFactory creates a http client factory with a customizable client +// See `InstallProtocol` to install and override default http client. +// Unless a properly initialized client is given, it will fall back into `http.DefaultClient`. +func NewGitUploadPackServiceFactory(client *http.Client) common.GitUploadPackServiceFactory { + return func(endpoint common.Endpoint) common.GitUploadPackService { + return newGitUploadPackService(endpoint, client) + } +} + +func newGitUploadPackService(endpoint common.Endpoint, client *http.Client) common.GitUploadPackService { + if client == nil { + client = http.DefaultClient + } s := &GitUploadPackService{ - client: http.DefaultClient, + client: client, endpoint: endpoint, } - s.setBasicAuthFromEndpoint() return s } -// Connect has not any effect, is here just for meet the interface +// Connect has not any effect, is here to satisfy interface func (s *GitUploadPackService) Connect() error { return nil } @@ -54,7 +69,7 @@ func (s *GitUploadPackService) setBasicAuthFromEndpoint() { // SetAuth sets the AuthMethod func (s *GitUploadPackService) SetAuth(auth common.AuthMethod) error { - httpAuth, ok := auth.(HTTPAuthMethod) + httpAuth, ok := auth.(AuthMethod) if !ok { return common.ErrInvalidAuthMethod } @@ -139,7 +154,8 @@ func (s *GitUploadPackService) doRequest(method, url string, content *strings.Re return nil, plumbing.NewUnexpectedError(err) } - if err := NewHTTPError(res); err != nil { + if err := NewErr(res); err != nil { + _ = res.Body.Close() return nil, err } @@ -168,7 +184,7 @@ func (s *GitUploadPackService) applyAuthToRequest(req *http.Request) { } // Disconnect do nothing -func (s *GitUploadPackService) Disconnect() (err error) { +func (s *GitUploadPackService) Disconnect() error { return nil } diff --git a/plumbing/client/http/git_upload_pack_test.go b/plumbing/client/http/git_upload_pack_test.go index a50dbdf..8010cea 100644 --- a/plumbing/client/http/git_upload_pack_test.go +++ b/plumbing/client/http/git_upload_pack_test.go @@ -1,7 +1,9 @@ package http import ( + "crypto/tls" "io/ioutil" + "net/http" . "gopkg.in/check.v1" "gopkg.in/src-d/go-git.v4/plumbing" @@ -30,6 +32,18 @@ func (s *RemoteSuite) TestNewGitUploadPackServiceAuth(c *C) { c.Assert(auth.String(), Equals, "http-basic-auth - foo:*******") } +func (s *RemoteSuite) TestNewGitUploadPackServiceFactory(c *C) { + e, err := common.NewEndpoint("https://foo:bar@github.com/git-fixtures/basic") + c.Assert(err, IsNil) + + roundTripper := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + client := &http.Client{Transport: roundTripper} + r := NewGitUploadPackServiceFactory(client)(e).(*GitUploadPackService) + + c.Assert(r.auth.String(), Equals, "http-basic-auth - foo:*******") + c.Assert(r.client.Transport, Equals, roundTripper) +} + func (s *RemoteSuite) TestConnect(c *C) { r := NewGitUploadPackService(s.Endpoint) c.Assert(r.Connect(), IsNil) diff --git a/plumbing/client/ssh/git_upload_pack.go b/plumbing/client/ssh/git_upload_pack.go index e2b73fd..db7fa93 100644 --- a/plumbing/client/ssh/git_upload_pack.go +++ b/plumbing/client/ssh/git_upload_pack.go @@ -84,11 +84,7 @@ func (s *GitUploadPackService) setAuthFromEndpoint() error { var err error s.auth, err = NewSSHAgentAuth(u) - if err != nil { - return err - } - - return nil + return err } // SetAuth sets the AuthMethod @@ -105,7 +101,7 @@ func (s *GitUploadPackService) SetAuth(auth common.AuthMethod) error { // Info returns the GitUploadPackInfo of the repository. The client must be // connected with the repository (using the ConnectWithAuth() method) before // using this method. -func (s *GitUploadPackService) Info() (i *common.GitUploadPackInfo, err error) { +func (s *GitUploadPackService) Info() (*common.GitUploadPackInfo, error) { if !s.connected { return nil, ErrNotConnected } @@ -125,12 +121,12 @@ func (s *GitUploadPackService) Info() (i *common.GitUploadPackInfo, err error) { return nil, err } - i = common.NewGitUploadPackInfo() + i := common.NewGitUploadPackInfo() return i, i.Decode(bytes.NewReader(out)) } // Disconnect the SSH client. -func (s *GitUploadPackService) Disconnect() (err error) { +func (s *GitUploadPackService) Disconnect() error { if !s.connected { return ErrNotConnected } @@ -142,7 +138,7 @@ func (s *GitUploadPackService) Disconnect() (err error) { // SSH session on a connected GitUploadPackService, sends the given // upload request to the server and returns a reader for the received // packfile. Closing the returned reader will close the SSH session. -func (s *GitUploadPackService) Fetch(req *common.GitUploadPackRequest) (rc io.ReadCloser, err error) { +func (s *GitUploadPackService) Fetch(req *common.GitUploadPackRequest) (io.ReadCloser, error) { if !s.connected { return nil, ErrNotConnected } @@ -243,7 +239,7 @@ func sendHaves(w io.Writer, req *common.GitUploadPackRequest) error { e := pktline.NewEncoder(w) for _, have := range req.Haves { if err := e.Encodef("have %s\n", have); err != nil { - return fmt.Errorf("sending haves for %q: err ", have, err) + return fmt.Errorf("sending haves for %q: %s", have, err) } } @@ -60,11 +60,8 @@ func (r *Remote) connectUploadPackService() error { func (r *Remote) retrieveUpInfo() error { var err error - if r.upInfo, err = r.upSrv.Info(); err != nil { - return err - } - - return nil + r.upInfo, err = r.upSrv.Info() + return err } // Info returns the git-upload-pack info diff --git a/remote_test.go b/remote_test.go index f129c68..bfda15d 100644 --- a/remote_test.go +++ b/remote_test.go @@ -1,12 +1,18 @@ package git import ( + "crypto/tls" + "fmt" "io" "io/ioutil" + "net/http" "os" + "time" "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/client" + githttp "gopkg.in/src-d/go-git.v4/plumbing/client/http" "gopkg.in/src-d/go-git.v4/plumbing/storer" "gopkg.in/src-d/go-git.v4/storage/filesystem" "gopkg.in/src-d/go-git.v4/storage/memory" @@ -202,3 +208,43 @@ func (s *RemoteSuite) TestString(c *C) { "foo\thttps://github.com/git-fixtures/basic.git (push)", ) } + +// Here is an example to configure http client according to our own needs. +func Example_customHTTPClient() { + const url = "https://github.com/git-fixtures/basic.git" + + // Create a custom http(s) client with your config + customClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, // accept any certificate (might be useful for testing) + Timeout: 15 * time.Second, // 15 second timeout + CheckRedirect: func(req *http.Request, via []*http.Request) error { // don't follow redirect + return http.ErrUseLastResponse + }, + } + + // Override http(s) default protocol to use our custom client + clients.InstallProtocol( + "https", + githttp.NewGitUploadPackServiceFactory(customClient)) + + // Create an in-memory repository + r := NewMemoryRepository() + + // Clone repo + if err := r.Clone(&CloneOptions{URL: url}); err != nil { + panic(err) + } + + // Retrieve the branch pointed by HEAD + head, err := r.Head() + if err != nil { + panic(err) + } + + // Print latest commit hash + fmt.Println(head.Hash()) + // Output: + // 6ecf0ef2c2dffb796033e5a02219af86ec6584e5 +} |