| Index: go/src/infra/libs/gitiles/gitiles.go
|
| diff --git a/go/src/infra/libs/gitiles/gitiles.go b/go/src/infra/libs/gitiles/gitiles.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ea7045ac0dc13452be305fb94ac81032c7f39190
|
| --- /dev/null
|
| +++ b/go/src/infra/libs/gitiles/gitiles.go
|
| @@ -0,0 +1,155 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package gitiles
|
| +
|
| +import (
|
| + "io"
|
| + "net/http"
|
| + "reflect"
|
| + "strings"
|
| +
|
| + "infra/libs/git"
|
| +)
|
| +
|
| +// Types ///////////////////////////////////////////////////////////////////////
|
| +
|
| +// Gitiles wraps one Gitiles server. Use NewGitiles to create one.
|
| +type Gitiles struct {
|
| + url string
|
| + requests chan<- request
|
| +}
|
| +
|
| +// Constructors ///////////////////////////////////////////////////////////////
|
| +
|
| +// NewGitiles creates a new Gitiles instance for the given |url|, and a maximum
|
| +// number of concurrent requests.
|
| +func NewGitiles(url string, maxConnections int) *Gitiles {
|
| + // TODO(iannucci): make a separate http.Client?
|
| + // TODO(iannucci): have a way to destroy the Gitiles instance?
|
| + requestChan := make(chan request, maxConnections)
|
| + ret := &Gitiles{
|
| + url: strings.TrimRight(url, "/"),
|
| + requests: requestChan,
|
| + }
|
| + for i := 0; i < maxConnections; i++ {
|
| + go ret.requestProcessor(requestChan)
|
| + }
|
| + return ret
|
| +}
|
| +
|
| +// Member functions ////////////////////////////////////////////////////////////
|
| +
|
| +// URL returns the base url for this Gitiles service wrapper
|
| +func (g *Gitiles) URL() string { return g.url }
|
| +
|
| +// JSON Returns an instance of target or an error.
|
| +//
|
| +// Example:
|
| +// data := map[string]int{}
|
| +// result, err := g.JSON(data, "some", "url", "pieces")
|
| +// if err != nil { panic(err) }
|
| +// data = *result.(*map[string]int)
|
| +func (g *Gitiles) JSON(target interface{}, pieces ...string) (interface{}, error) {
|
| + reply := make(chan jsonResult, 1)
|
| + g.requests <- jsonRequest{
|
| + strings.Join(pieces, "/"),
|
| + reflect.TypeOf(target),
|
| + reply,
|
| + }
|
| + rslt := <-reply
|
| + return rslt.dataPtr, rslt.err
|
| +}
|
| +
|
| +// Text returns the decoded text data or an error. It will load the given path
|
| +// with format=TEXT and apply the base64 decoding.
|
| +func (g *Gitiles) Text(pieces ...string) ([]byte, error) {
|
| + reply := make(chan textResult, 1)
|
| + g.requests <- textRequest{
|
| + strings.Join(pieces, "/"),
|
| + reply,
|
| + }
|
| + rslt := <-reply
|
| + return rslt.data, rslt.err
|
| +}
|
| +
|
| +// GetObjectFromPath returns the git Object at the given commit:path, or an error.
|
| +// This will hit the url:
|
| +// {g.url}/+/{committish}/path/pieces...?format=TEXT
|
| +// Passing NO pieces will return a git.Commit
|
| +// Passing A blank piece (e.g. empty string) will return a git.Tree for the root
|
| +// tree.
|
| +// Passing non-blank pieces will return a git.Tree or git.Blob depending on
|
| +// the path.
|
| +func (g *Gitiles) GetObjectFromPath(committish string, pathPieces ...string) (git.InternableObject, error) {
|
| + ret := make(chan objectResult, 1)
|
| + g.requests <- objRequest{
|
| + textRequest{
|
| + strings.Join(append([]string{"+", committish}, pathPieces...), "/"),
|
| + nil,
|
| + },
|
| + ret,
|
| + }
|
| + rslt := <-ret
|
| + return rslt.object, rslt.err
|
| +}
|
| +
|
| +// GetCommitDiff returns the git.TreeDiff from the given committish.
|
| +func (g *Gitiles) GetCommitDiff(committish string) (git.TreeDiff, error) {
|
| + rslt, err := g.JSON(jsonCommitDiff{}, "+", committish)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + diff := *rslt.(*jsonCommitDiff)
|
| + return diff.ToResult()
|
| +}
|
| +
|
| +func (g *Gitiles) GetTextCommitDiff(id *git.ObjectID) (string, error) {
|
| + rslt, err := g.Text("+", id.String()+"^!")
|
| + if err != nil {
|
| + return "", err
|
| + }
|
| + return string(rslt), nil
|
| +}
|
| +
|
| +// Private /////////////////////////////////////////////////////////////////////
|
| +
|
| +type request interface {
|
| + Process(rsp *http.Response, err error)
|
| + Method() string
|
| + URLPath() string
|
| + Body() io.Reader
|
| +}
|
| +
|
| +func (g *Gitiles) requestProcessor(queue <-chan request) {
|
| + // Launched as a goroutine to avoid blocking the request processor.
|
| + for r := range queue {
|
| + r := r
|
| + if r == nil {
|
| + continue
|
| + }
|
| +
|
| + var req *http.Request
|
| + req, err := http.NewRequest(r.Method(), g.url+"/"+r.URLPath(), r.Body())
|
| + if err != nil {
|
| + r.Process(nil, err)
|
| + continue
|
| + }
|
| +
|
| + rsp, err := http.DefaultClient.Do(req)
|
| + if err != nil {
|
| + r.Process(nil, err)
|
| + continue
|
| + }
|
| +
|
| + e := StatusError(rsp.StatusCode)
|
| + if e.Bad() {
|
| + err = e
|
| + }
|
| + func() {
|
| + defer rsp.Body.Close()
|
| + r.Process(rsp, err)
|
| + }()
|
| + }
|
| +}
|
|
|