Skip to content
quarry Artifact [1ba6f5b3cd]
Login

Artifact [1ba6f5b3cd]

Artifact 1ba6f5b3cdfbfa93ff14965524364d89ea55ad31d7b23b064ebba5729cbea487:


package zitcli

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	"craftdesign.group/quarry/internal/lease"
)

// GitImport is what one `zit import-git` run reported: how many
// check-ins it added, and one line per upstream head that moved away
// from its old tip or vanished (a force-push upstream; the check-ins it
// left behind stay).
type GitImport struct {
	Added int
	Moved []string
}

var importAdded = regexp.MustCompile(`added (\d+) check-in`)

func parseGitImport(out string) GitImport {
	var r GitImport
	for _, l := range strings.Split(out, "\n") {
		l = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(l), "import-git:"))
		if m := importAdded.FindStringSubmatch(l); m != nil {
			r.Added, _ = strconv.Atoi(m[1])
			continue
		}
		if strings.HasPrefix(l, "head ") {
			r.Moved = append(r.Moved, l)
		}
	}
	return r
}

// ImportGit makes the store at dest from the Git work tree at source:
// the first import of a repository that follows a Git upstream (#358).
// Flat manifests, so plain fossil can clone the result as it can any
// store quarry makes. dest must not exist; zit writes it only when the
// import completes. No lease: the store is not served yet.
func ImportGit(ctx context.Context, source, dest string, env []string) (GitImport, error) {
	return runImportGit(ctx, "", source, dest, env, false)
}

// ImportGitInto adds the upstream commits the store does not yet hold
// (zit import-git --into). A write under the store's lease; on any
// failure zit leaves the store as it was.
func (c *Client) ImportGitInto(tok lease.Token, ctx context.Context, source string, env []string) (GitImport, error) {
	if err := tok.Err(); err != nil {
		return GitImport{}, err
	}
	ctx, stop := underLease(ctx, tok)
	defer stop()
	return runImportGit(ctx, repoName(c.repo), source, c.repo, env, true)
}

func runImportGit(ctx context.Context, repo, source, dest string, env []string, into bool) (GitImport, error) {
	args := []string{"import-git", source, dest, "--no-submanifests"}
	if into {
		args = append(args, "--into")
	}
	if err := checkArgs(args); err != nil {
		return GitImport{}, err
	}
	if repo == "" {
		repo = repoName(dest)
	}
	cmd, done := spawnUnattended(ctx, repo, "git-import", args...)
	defer done()
	if cmd.Err != nil {
		return GitImport{}, cmd.Err
	}
	cmd.Env = append(os.Environ(), env...)
	OwnGroup(cmd)
	cmd.WaitDelay = waitDelay
	var out bytes.Buffer
	cmd.Stdout, cmd.Stderr = &out, &out
	err := cmd.Run()
	text := strings.TrimSpace(out.String())
	if err != nil {
		return GitImport{}, fmt.Errorf("zit import-git: %s", lastLine(text, err))
	}
	return parseGitImport(text), nil
}

// lastLine is the import's last line of output, which is its refusal
// when it refuses, else the process error.
func lastLine(text string, err error) string {
	if i := strings.LastIndexByte(text, '\n'); i >= 0 {
		text = text[i+1:]
	}
	text = strings.TrimSpace(strings.TrimPrefix(text, "import-git:"))
	if text == "" {
		return err.Error()
	}
	return text
}