//go:build integration package main // The fetch step's merge on real git repositories (issue 1676); preview_test.go proves the commands with a fake runner. import ( "context" "os" "os/exec" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/forgekey" ) func git(t *testing.T, dir string, args ...string) string { t.Helper() cmd := exec.Command("git", append([]string{"-C", dir, "-c", "user.name=t", "-c", "user.email=t@example.invalid", "-c", "commit.gpgsign=false"}, args...)...) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("git %v: %v\n%s", args, err, out) } return strings.TrimSpace(string(out)) } func TestRealFetchMergesAConflictAndAPlainFetch(t *testing.T) { root := t.TempDir() bare, work := filepath.Join(root, "forge", "o", "r.git"), filepath.Join(root, "work") os.MkdirAll(bare, 0o755) os.MkdirAll(work, 0o755) git(t, bare, "init", "-q", "--bare") git(t, work, "init", "-q", "-b", "main") files := typeScriptModule(`{"dependencies":{"@novox/mesh-sdk":"^0.1.6"}}`) files["a.txt"] = "one\ntwo\nthree\n" for n, c := range files { write(t, filepath.Join(work, n), c, 0o644) } git(t, work, "add", ".") git(t, work, "commit", "-q", "-m", "fork") fork := git(t, work, "rev-parse", "HEAD") change := func(from, ref string, f map[string]string) string { git(t, work, "checkout", "-q", "-B", "tmp", from) for n, c := range f { write(t, filepath.Join(work, n), c, 0o644) } git(t, work, "add", ".") git(t, work, "commit", "-q", "-m", "c") git(t, work, "push", "-q", "-f", bare, "HEAD:"+ref) return git(t, work, "rev-parse", "HEAD") } trunk := change(fork, "refs/heads/main", map[string]string{"a.txt": "one\ntwo\nTRUNK\n"}) clean := change(fork, "refs/pull/1/head", map[string]string{"modules/m/extra.ts": "export {}\n"}) clash := change(fork, "refs/pull/2/head", map[string]string{"a.txt": "one\ntwo\nHEAD\n"}) m, _, _ := newMachine(t) m.Run = forgekey.ExecRunner m.RemoteBase = "file://" + filepath.Join(root, "forge") + "/" m.FetchOptions = []buildstep.Option{buildstep.AllowFileRemote()} out, err := m.fetch(context.Background(), map[string]any{"repository": "o/r", "commit": clean, "ref": "refs/pull/1/head", "base": trunk}) if err != nil { t.Fatal(err) } r := out.(FetchResult) tree := filepath.Join(m.Work, r.Workspace, "tree") if r.Merge == "" || strings.Join(r.Changed, ",") != "modules/m/extra.ts" || len(r.Conflicts) != 0 || len(r.Modules) != 1 { t.Fatalf("clean: %+v", r) } if want := git(t, tree, "rev-parse", r.Merge+":modules/m"); r.Trees["modules/m"].Tree != want { t.Errorf("tree = %s, want %s", r.Trees["modules/m"].Tree, want) } if got := git(t, tree, "rev-parse", "HEAD"); got != r.Merge { t.Errorf("the workspace is at %s, want the merge %s", got, r.Merge) } out, err = m.fetch(context.Background(), map[string]any{"repository": "o/r", "commit": clash, "ref": "refs/pull/2/head", "base": trunk}) if err != nil { t.Fatalf("a conflict failed the step: %v", err) } r = out.(FetchResult) if r.Merge != "" || strings.Join(r.Conflicts, ",") != "a.txt" { t.Fatalf("conflict: %+v", r) } if got := git(t, filepath.Join(m.Work, r.Workspace, "tree"), "rev-parse", "HEAD"); got != trunk { t.Errorf("after a conflict the workspace is at %s, want the trunk %s", got, trunk) } out, err = m.fetch(context.Background(), map[string]any{"repository": "o/r", "commit": clean, "ref": "refs/pull/1/head"}) if err != nil || out.(FetchResult).Merge != "" || out.(FetchResult).Trees != nil { t.Errorf("no base: %+v %v", out, err) } }