Skip to content
quarry Artifact [0bb26adfcc]
Login

Artifact [0bb26adfcc]

Artifact 0bb26adfcc1e4baa59abfec2038a3b55e60a8c8f64eb5ebeaa5ead3e8fe5b242:


package api

import (
	"errors"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/zitcli"
)

// A landing may be stacked on a branch it was never built on: it lands
// after it, and its checks test it merged onto it. What it may not be
// stacked on is a branch it shares no history with, since no merge
// between the two exists. The store is real (a Git repository with an
// orphan branch, imported), because merge bases are the store's answer.
func TestStackingNeedsSharedHistoryNotAncestry(t *testing.T) {
	for _, bin := range []string{"git", "zit"} {
		if _, err := exec.LookPath(bin); err != nil {
			t.Skip(bin + " not on PATH")
		}
	}
	dir := t.TempDir()
	run := func(name string, args ...string) string {
		t.Helper()
		cmd := exec.Command(name, args...)
		cmd.Dir = dir
		cmd.Env = append(cmd.Environ(), "GIT_AUTHOR_NAME=a", "GIT_AUTHOR_EMAIL=a@b", "GIT_COMMITTER_NAME=a", "GIT_COMMITTER_EMAIL=a@b")
		out, err := cmd.CombinedOutput()
		if err != nil {
			t.Fatalf("%s %v: %v\n%s", name, args, err, out)
		}
		return string(out)
	}
	run("git", "init", "-q", "-b", "main", "g")
	git := func(args ...string) { run("git", append([]string{"-C", "g"}, args...)...) }
	git("commit", "-q", "--allow-empty", "-m", "A")
	git("checkout", "-q", "-b", "side")
	git("commit", "-q", "--allow-empty", "-m", "C")
	git("checkout", "-q", "-b", "beside", "main")
	git("commit", "-q", "--allow-empty", "-m", "E")
	git("checkout", "-q", "--orphan", "alien")
	git("commit", "-q", "--allow-empty", "-m", "D")
	git("checkout", "-q", "main")
	run("zit", "import-git", "g", "s")
	// One manifest per branch tip, by the store's own hash, so the
	// index and the store agree on what each branch is.
	var batch []index.Parsed
	for _, line := range strings.Split(strings.TrimSpace(run("zit", "timeline", "-F", "oneline", "-R", "s")), "\n") {
		f := strings.Fields(line)
		if len(f) < 4 {
			continue
		}
		batch = append(batch, index.Parsed{Hash: f[0], Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: f[1], User: "a", Comment: f[3],
			Tags: []artifact.Tag{{Name: "*branch", Target: "*", Value: f[2]}, {Name: "*sym-" + f[2], Target: "*"}}}})
	}
	open := func(hash, branch string) index.Parsed {
		return index.Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Forum, ThreadTitle: branch,
			Date: "2026-01-02T10:00:00", User: "ada",
			Body: []byte("zit-landing: 1\nbranch: " + branch + "\ntarget: trunk\n\nready\n")}}
	}
	batch = append(batch,
		open("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0", "side"),
		open("b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0", "beside"),
		open("c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0", "alien"))
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	if err := ix.FoldNow(batch); err != nil {
		t.Fatal(err)
	}
	r := ops.NewRegistry()
	if err := registerLandingRetarget(r, Deps{Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(filepath.Join(dir, "s"))}})}); err != nil {
		t.Fatal(err)
	}
	// side onto beside: siblings off trunk, neither built on the other.
	// Past the history check, the write fails for want of a zit record
	// of the landing; only a refusal of the move itself is a failure.
	if err := retarget(r, as("ada", "i"), "a0a0a0a0a0a0", "!b0b0b0b0b0b0"); errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: a sibling was refused: %v", err)
	}
	if err := retarget(r, as("ada", "i"), "a0a0a0a0a0a0", "!c0c0c0c0c0c0"); !errors.Is(err, ops.ErrInvalid) || !strings.Contains(err.Error(), "no history") {
		t.Errorf("FAIL: a branch with no shared history was accepted: %v", err)
	}
}