Skip to content
quarry Artifact [d716d72658]
Login

Artifact [d716d72658]

Artifact d716d72658a7f6026274f4729eda9c22cb59b376ba9493d75bed107c9f1d5495:


package api

import (
	"context"
	"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 check-in past largeDiffLines is answered by its file list and each
// file's counts, with no hunks computed; file= narrows zit's diff to
// that one path; unified=true is still the whole patch, for a script.
func TestALargeCheckinIsDiffedFileByFile(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	zrun(t, dir, "init")
	zwrite(t, dir, "a.txt", "one\n")
	zwrite(t, dir, "b.txt", "two\n")
	zrun(t, dir, "add", "a.txt", "b.txt")
	zrun(t, dir, "commit", "-m", "first", "--user", "u")
	base := ztip(t, dir, "trunk")
	zwrite(t, dir, "a.txt", "one\nmore a\n")
	zwrite(t, dir, "b.txt", "TWO\n")
	zrun(t, dir, "commit", "-m", "second", "--user", "u")
	tip := ztip(t, dir, "trunk")

	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([]index.Parsed{
		{Hash: base, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-10-30T10:00:00", User: "u", Comment: "first"},
			Changes: []string{"a.txt", "b.txt"}},
		{Hash: tip, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-10-30T11:00:00", User: "u", Comment: "second", Parents: []string{base}},
			Changes: []string{"a.txt", "b.txt"}, Lines: map[string]index.LineCount{"a.txt": {Added: 1}, "b.txt": {Added: 1, Removed: 1}}},
	}); err != nil {
		t.Fatal(err)
	}
	r := ops.NewRegistry()
	deps := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: zitcli.New(filepath.Join(dir, ".zit"))}})}
	if err := RegisterAll(r, deps); err != nil {
		t.Fatal(err)
	}
	defer func(n int) { largeDiffLines = n }(largeDiffLines)
	largeDiffLines = 2
	diffOf := func(in diffGetIn) diffGetOut {
		t.Helper()
		in.Repo, in.Hash = "test", tip
		out, err := r.Invoke(context.Background(), "checkin.diff", in)
		if err != nil {
			t.Fatal(err)
		}
		return out.(diffGetOut)
	}
	whole := diffOf(diffGetIn{})
	if !whole.Large || len(whole.Files) != 2 || len(whole.Structured) != 0 || whole.Counts["b.txt"].Removed != 1 || whole.TotalLines != 3 {
		t.Errorf("FAIL: a large check-in answered large=%v files=%d hunks=%d counts=%v total=%d",
			whole.Large, len(whole.Files), len(whole.Structured), whole.Counts, whole.TotalLines)
	}
	one := diffOf(diffGetIn{File: "b.txt"})
	if one.Large || len(one.Structured) != 1 || one.Structured[0].Path != "b.txt" {
		t.Errorf("FAIL: file=b.txt answered large=%v with %d files", one.Large, len(one.Structured))
	}
	// A large check-in's whole patch is not built into one answer: the
	// op refuses and points at the streamed download (jva, ^3920).
	for _, in := range []diffGetIn{{Unified: true}, {Unified: true, Raw: true}} {
		in.Repo, in.Hash = "test", tip
		if _, err := r.Invoke(context.Background(), "checkin.diff", in); err == nil || !strings.Contains(err.Error(), "/patch") {
			t.Errorf("FAIL: a large check-in's whole unified diff answered (%v), not the pointer to /patch", err)
		}
	}
	largeDiffLines = 20000
	if small := diffOf(diffGetIn{}); small.Large || len(small.Structured) != 2 {
		t.Errorf("FAIL: a small check-in answered large=%v with %d files", small.Large, len(small.Structured))
	}
	// A small one still answers its whole patch, raw: nothing parsed.
	raw := diffOf(diffGetIn{Unified: true, Raw: true})
	if len(raw.Structured) != 0 || !strings.Contains(raw.Unified, "a.txt") || !strings.Contains(raw.Unified, "b.txt") {
		t.Errorf("FAIL: a small check-in's raw patch answered %d parsed files, %d bytes", len(raw.Structured), len(raw.Unified))
	}
	// The streamed download writes the same bytes, and names the check-in
	// before it writes any.
	var buf strings.Builder
	named := ""
	if err := deps.PatchTo(context.Background(), "test", tip, "", false, func(to string) {
		if buf.Len() != 0 {
			t.Error("FAIL: named after bytes were written")
		}
		named = to
	}, &buf); err != nil {
		t.Fatal(err)
	}
	if buf.String() != raw.Unified || named != tip {
		t.Errorf("FAIL: the streamed patch (%d bytes, named %.12s) differs from checkin.diff's (%d bytes)", buf.Len(), named, len(raw.Unified))
	}

	// A range diff has no index count; it is sized by its file list.
	defer func(n int) { largeDiffFiles = n }(largeDiffFiles)
	largeDiffFiles = 1
	if rng := diffOf(diffGetIn{From: base}); !rng.Large || len(rng.Files) != 2 || len(rng.Structured) != 0 {
		t.Errorf("FAIL: a range past largeDiffFiles answered large=%v files=%d hunks=%d", rng.Large, len(rng.Files), len(rng.Structured))
	}
	largeDiffFiles = 500
	if rng := diffOf(diffGetIn{From: base}); rng.Large || len(rng.Structured) != 2 {
		t.Errorf("FAIL: a small range answered large=%v with %d files", rng.Large, len(rng.Structured))
	}
}