Skip to content
quarry Artifact [88d8adc6d5]
Login

Artifact [88d8adc6d5]

Artifact 88d8adc6d52ebe558a69cd28c2a9edfaab595a8f879ffebafb604a04056323d8:


package runner

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// ZitCheckout clones the repository once per workspace and moves it to
// each check-in after that, the same refresh the forge's own worker
// does: pull, discard anything the last task left, update.
type ZitCheckout struct {
	Zit  string // the zit binary
	URL  string // the repository's URL on the forge
	User string // for a private repository: the account to fetch as
	// Password is that account's sync token. zit is handed it as
	// ZIT_PASSWORD, and a premerge pair's archive, which is a tree the
	// forge merges and not a check-in a clone could update to, is
	// fetched with it directly.
	Password string
}

func (z ZitCheckout) run(ctx context.Context, dir string, args ...string) error {
	cmd := exec.CommandContext(ctx, z.Zit, args...)
	cmd.Dir = dir
	if z.Password != "" {
		// The runner is told the token as QUARRY_RUNNER_PASSWORD; zit
		// reads ZIT_PASSWORD. Without this a private repository's clone
		// had a --user and no password, whatever the runner was given.
		cmd.Env = append(os.Environ(), "ZIT_PASSWORD="+z.Password)
	}
	out, err := cmd.CombinedOutput()
	if err != nil {
		if z.User == "" && bytes.Contains(out, []byte("wants a login")) {
			return fmt.Errorf("zit %s: %v: %s: this repository is private: start the runner with --user ACCOUNT and that account's sync token in QUARRY_RUNNER_PASSWORD", args[0], err, bytes.TrimSpace(out))
		}
		return fmt.Errorf("zit %s: %v: %s", args[0], err, out)
	}
	return nil
}

// Checkout puts checkin's tree in dir. A premerge pair has no check-in
// to update to, so its merged tree comes from the forge's archive; the
// workspace is then a tree, and the next check-in reclones.
func (z ZitCheckout) Checkout(ctx context.Context, dir, checkin string) error {
	if IsPair(checkin) {
		return ArchiveCheckout{URL: z.URL, User: z.User, Password: z.Password}.Checkout(ctx, dir, checkin)
	}
	auth := []string{}
	if z.User != "" {
		auth = []string{"--user", z.User}
	}
	if _, err := os.Stat(filepath.Join(dir, ".zit")); err == nil {
		if err := z.run(ctx, dir, append([]string{"pull", z.URL}, auth...)...); err == nil {
			_ = z.run(ctx, dir, "revert")
			_ = z.run(ctx, dir, "clean")
			if err := z.run(ctx, dir, "update", checkin); err == nil {
				return nil
			}
		}
	}
	// Anything else is recloned into an empty directory: a workspace that
	// will not refresh, and a pair's archive tree, which has no .zit and
	// whose files zit's open will not write over.
	os.RemoveAll(dir)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return err
	}
	if err := z.run(ctx, dir, append([]string{"clone", z.URL, "."}, auth...)...); err != nil {
		return err
	}
	if err := z.run(ctx, dir, "update", checkin); err != nil {
		return z.run(ctx, dir, "open", checkin)
	}
	return nil
}