Skip to content
go-libfossil Artifact [f5e452e574]
Login

Artifact [f5e452e574]

Artifact f5e452e574d2fed9b748d3bc196381527ca349a2d69a653b5ac7a78def280c21:


package conformance

import (
	"errors"
	"fmt"
	"slices"
	"testing"
	"time"

	"github.com/danmestas/go-libfossil/internal/checkout"
)

func scWorktreeCompleteCases() []scCase {
	return []scCase{
		{Case{ID: "sparsecone.4.4.record-only-merge", Req: "SC-4.4-1"}, scRecordOnlyMerge},
		{Case{ID: "sparsecone.4.4.record-only-conflict", Req: "SC-4.4-1"}, scRecordOnlyConflict},
	}
}

func TestSparseWorktreeComplete(t *testing.T) {
	for _, c := range scWorktreeCompleteCases() {
		t.Run(c.ID, func(t *testing.T) {
			if err := c.run(t); err != nil {
				t.Fatal(err)
			}
		})
	}
}

func scRecordOnlyMerge(t *testing.T) error {
	for _, tree := range []bool{false, true} {
		f := newSCRepo(t, tree)
		remote := make(map[string]string, len(scFiles)+1)
		for name, text := range scFiles {
			remote[name] = text
		}
		remote["docs/d.txt"] = "remote docs\n"
		remote["tools/new.txt"] = "remote addition\n"
		delete(remote, "tools/t.c")
		remote["src/a.c"] = "remote source\n"
		mid := f.commit(t, remote, f.c1)
		co := f.open(t, scDefault(t), f.c1)
		if _, err := co.Merge(checkout.MergeOpts{Version: mid}); err != nil {
			return err
		}
		if got := scOnDisk(t, co.Dir()); !slices.Equal(got, scDefaultCovered) {
			return fmt.Errorf("merge materialized outside cone: %q", got)
		}
		rid, _, err := co.Commit(checkout.CommitOpts{Message: "merge", User: "u", Time: f.when.Add(time.Hour)})
		if err != nil {
			return err
		}
		got, err := scContent(f.r, rid)
		if err != nil {
			return err
		}
		if err := scSameContent("record-only merge", got, remote); err != nil {
			return err
		}
	}
	return nil
}

func scRecordOnlyConflict(t *testing.T) error {
	f := newSCRepo(t, false)
	files := func(text string) map[string]string {
		out := make(map[string]string, len(scFiles))
		for name, text := range scFiles {
			out[name] = text
		}
		out["docs/d.txt"] = text
		return out
	}
	ours := f.commit(t, files("ours\n"), f.c1)
	theirs := f.commit(t, files("theirs\n"), f.c1)
	co := f.open(t, scDefault(t), ours)
	if _, err := co.Merge(checkout.MergeOpts{Version: theirs}); !errors.Is(err, checkout.ErrOutsideCone) {
		return fmt.Errorf("outside both-changed merge = %v, want widening refusal", err)
	}
	if got := scOnDisk(t, co.Dir()); !slices.Equal(got, scDefaultCovered) {
		return fmt.Errorf("outside conflict materialized a file: %q", got)
	}
	conflicts, err := co.Conflicts()
	if err != nil || len(conflicts) != 0 {
		return fmt.Errorf("refused merge left conflicts = %q: %v", conflicts, err)
	}
	wider, err := co.Cone().With([]string{"docs"}, nil)
	if err != nil {
		return err
	}
	if _, err := co.SetCone(wider); err != nil {
		return fmt.Errorf("widen to resolve record-only conflict: %w", err)
	}
	if _, err := co.Merge(checkout.MergeOpts{Version: theirs}); err != nil {
		return err
	}
	conflicts, err = co.Conflicts()
	if err != nil || !slices.Equal(conflicts, []string{"docs/d.txt"}) {
		return fmt.Errorf("in-cone conflicts = %q: %v", conflicts, err)
	}
	_, _, err = co.Commit(checkout.CommitOpts{Message: "unresolved", User: "u", Time: f.when.Add(time.Hour)})
	if !errors.Is(err, checkout.ErrUnresolvedConflicts) {
		return fmt.Errorf("widened conflict commit = %v", err)
	}
	scWrite(t, co.Dir(), "docs/d.txt", "resolved\n")
	rid, _, err := co.Commit(checkout.CommitOpts{Message: "resolved", User: "u", Time: f.when.Add(2 * time.Hour)})
	if err != nil {
		return err
	}
	got, err := scContent(f.r, rid)
	if err != nil {
		return err
	}
	return scSameContent("resolved record-only conflict", got, files("resolved\n"))
}