| package git |
| |
| import ( |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "strings" |
| |
| "gopkg.in/src-d/go-git.v1/pktline" |
| |
| "github.com/sourcegraph/go-vcsurl" |
| ) |
| |
| type Client struct { |
| url string |
| client *http.Client |
| } |
| |
| func NewClient(url string) *Client { |
| vcs, _ := vcsurl.Parse(url) |
| return &Client{url: vcs.Link(), client: &http.Client{}} |
| } |
| |
| func (c *Client) Refs() (*Refs, error) { |
| req, _ := c.buildRequest( |
| "GET", |
| fmt.Sprintf("%s/info/refs?service=git-upload-pack", c.url), |
| nil, |
| ) |
| |
| res, err := c.client.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| if res.StatusCode >= 400 { |
| return nil, &ErrNotFound{c.url} |
| } |
| |
| defer res.Body.Close() |
| d := pktline.NewDecoder(res.Body) |
| |
| content, err := d.ReadAll() |
| if err != nil { |
| return nil, err |
| } |
| |
| return c.buildRefsFromContent(content), nil |
| } |
| |
| func (c *Client) buildRefsFromContent(content []string) *Refs { |
| refs := &Refs{branches: make(map[string]string, 0)} |
| for _, line := range content { |
| if line[0] == '#' { |
| continue |
| } |
| |
| if refs.defaultBranch == "" { |
| refs.defaultBranch = c.getDefaultBranchFromLine(line) |
| } else { |
| commit, branch := c.getCommitAndBranch(line) |
| refs.branches[branch] = commit |
| } |
| } |
| |
| return refs |
| } |
| |
| func (c *Client) getDefaultBranchFromLine(line string) string { |
| args, _ := url.ParseQuery(strings.Replace(line, " ", "&", -1)) |
| |
| link, ok := args["symref"] |
| if !ok { |
| return "" |
| } |
| |
| parts := strings.Split(link[0], ":") |
| if len(parts) != 2 || parts[0] != "HEAD" { |
| return "" |
| } |
| |
| return parts[1] |
| } |
| |
| func (c *Client) getCommitAndBranch(line string) (string, string) { |
| parts := strings.Split(strings.Trim(line, " \n"), " ") |
| if len(parts) != 2 { |
| return "", "" |
| } |
| |
| return parts[0], parts[1] |
| } |
| |
| func (c *Client) PackFile(want string) (io.ReadCloser, error) { |
| e := pktline.NewEncoder() |
| e.AddLine(fmt.Sprintf("want %s", want)) |
| e.AddFlush() |
| e.AddLine("done") |
| |
| req, err := c.buildRequest( |
| "POST", |
| fmt.Sprintf("%s/git-upload-pack", c.url), |
| e.GetReader(), |
| ) |
| if err != nil { |
| return nil, err |
| } |
| |
| res, err := c.client.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| |
| h := make([]byte, 8) |
| if _, err := res.Body.Read(h); err != nil { |
| return nil, err |
| } |
| |
| return res.Body, nil |
| } |
| |
| func (c *Client) buildRequest(method, url string, content *strings.Reader) (*http.Request, error) { |
| var req *http.Request |
| var err error |
| if content == nil { |
| req, err = http.NewRequest(method, url, nil) |
| } else { |
| req, err = http.NewRequest(method, url, content) |
| } |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| c.applyHeadersToRequest(req, content) |
| return req, nil |
| } |
| |
| func (c *Client) applyHeadersToRequest(req *http.Request, content *strings.Reader) { |
| req.Header.Add("User-Agent", "git/1.0") |
| req.Header.Add("Host", "github.com") |
| |
| if content == nil { |
| req.Header.Add("Accept", "*/*") |
| } else { |
| req.Header.Add("Accept", "application/x-git-upload-pack-result") |
| req.Header.Add("Content-Type", "application/x-git-upload-pack-request") |
| req.Header.Add("Content-Length", string(content.Len())) |
| } |
| } |
| |
| type Refs struct { |
| defaultBranch string |
| branches map[string]string |
| } |
| |
| func (r *Refs) DefaultBranch() string { |
| return r.defaultBranch |
| } |
| |
| func (r *Refs) DefaultBranchCommit() string { |
| return r.branches[r.defaultBranch] |
| } |
| |
| func (r *Refs) Branches() map[string]string { |
| return r.branches |
| } |
| |
| type ErrNotFound struct { |
| url string |
| } |
| |
| func (e *ErrNotFound) Url() string { |
| return e.url |
| } |
| |
| func (e *ErrNotFound) Error() string { |
| return fmt.Sprintf("Unable to find %q", e.url) |
| } |