Skip to content
quarry Artifact [187dd16d28]
Login

Artifact [187dd16d28]

Artifact 187dd16d28dcaa321d93f3580b25b98e9f58fa91931f5e4203e12d256ccdbf32:


package runner

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

// ForgeBranch asks the forge which branch a check-in is on
// (GET /api/v1/r/<repo>/checkin/<hash>), for a runner that runs one
// branch only. The task's own input is not asked: it came from a
// pipeline the check-in under test may have written.
type ForgeBranch struct {
	API string // https://forge/api/v1/r/<repo>
	// User and Password, for a private repository, as ArchiveCheckout.
	User, Password string
	Client         *http.Client
}

func (f ForgeBranch) BranchOf(ctx context.Context, checkin string) (string, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", strings.TrimSuffix(f.API, "/")+"/checkin/"+url.PathEscape(checkin), nil)
	if err != nil {
		return "", err
	}
	if f.User != "" {
		req.SetBasicAuth(f.User, f.Password)
	}
	c := f.Client
	if c == nil {
		c = http.DefaultClient
	}
	resp, err := c.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("the forge answered %d for check-in %.12s", resp.StatusCode, checkin)
	}
	var body struct {
		Hash   string `json:"hash"`
		Branch string `json:"branch"`
	}
	if err := json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(&body); err != nil {
		return "", fmt.Errorf("check-in %.12s: %v", checkin, err)
	}
	if body.Hash != checkin {
		// A prefix the forge resolved, or another check-in: either way
		// not an answer about the one this task names.
		return "", fmt.Errorf("the forge answered for %.12s when asked about %.12s", body.Hash, checkin)
	}
	return body.Branch, nil
}

// Changed asks the forge for the paths checkin's branch changed since
// its fork point (GET /api/v1/r/<repo>/checkin/<hash>/diff?branch=true).
// A check-in on a branch with no fork point, trunk's own, is an error:
// there is no branch change to name.
func (f ForgeBranch) Changed(ctx context.Context, checkin string) ([]string, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", strings.TrimSuffix(f.API, "/")+"/checkin/"+url.PathEscape(checkin)+"/diff?branch=true&maxLines=1", nil)
	if err != nil {
		return nil, err
	}
	if f.User != "" {
		req.SetBasicAuth(f.User, f.Password)
	}
	c := f.Client
	if c == nil {
		c = http.DefaultClient
	}
	resp, err := c.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("the forge answered %d for the branch diff of %.12s", resp.StatusCode, checkin)
	}
	var body struct {
		To    string `json:"to"`
		Files []struct {
			Name string `json:"name"`
		} `json:"files"`
	}
	if err := json.NewDecoder(io.LimitReader(resp.Body, 64<<20)).Decode(&body); err != nil {
		return nil, fmt.Errorf("the branch diff of %.12s: %v", checkin, err)
	}
	if body.To != checkin {
		return nil, fmt.Errorf("the forge answered for %.12s when asked about %.12s", body.To, checkin)
	}
	paths := make([]string, 0, len(body.Files))
	for _, f := range body.Files {
		paths = append(paths, f.Name)
	}
	return paths, nil
}