e2e/migratemr_test.go
100 lines · 4133 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// TestMigratedMRHasADiff covers #128's concrete symptom: a merge request
11// replayed from a bundle used to arrive with an empty head, so `mr diff`
12// on it could only fail. The bundle carries the head and the merge base
13// now, and the head ref is set once the git objects are pushed.
14//
15// The merge request here is merged and its source branch deleted, which
16// is the case that actually breaks. While the branch still exists the
17// diff resolves through it and an empty head_sha is invisible — the first
18// version of this test made that mistake and passed without the fix.
19func TestMigratedMRHasADiff(t *testing.T) {
20 t.Parallel()
21 src := startInstance(t)
22 dst := startInstance(t)
23 key := src.newKey(t, "alice")
24 src.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
25 dst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
26
27 if _, errOut, code := src.ssh(t, key, "", "repo", "create", "alice/tool"); code != 0 {
28 t.Fatalf("repo create: %s", errOut)
29 }
30 env := src.gitEnv(key)
31 work := t.TempDir()
32 mustGit(t, work, env, "clone", src.sshURL("alice/tool"), "w")
33 dir := filepath.Join(work, "w")
34 os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n"), 0o644)
35 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
36 mustGit(t, dir, env, "add", ".")
37 mustGit(t, dir, env, "commit", "-q", "-m", "base")
38 mustGit(t, dir, env, "push", "-q", "origin", "main")
39
40 mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
41 os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n\nfunc F() {}\n"), 0o644)
42 mustGit(t, dir, env, "add", ".")
43 mustGit(t, dir, env, "commit", "-q", "-m", "add F")
44 mustGit(t, dir, env, "push", "-q", "origin", "feat")
45 if _, errOut, code := src.ssh(t, key, "", "mr", "create", "alice/tool",
46 "--source", "feat", "--target", "main", "--title", "'add F'"); code != 0 {
47 t.Fatalf("mr create: %s", errOut)
48 }
49 // Merge it and delete the branch, the way a finished merge request
50 // ends up. Now nothing but the recorded head says what it contained.
51 if _, errOut, code := src.ssh(t, key, "", "mr", "merge", "alice/tool", "1"); code != 0 {
52 t.Fatalf("merge: %s", errOut)
53 }
54 mustGit(t, dir, env, "push", "-q", "origin", "--delete", "feat")
55
56 // The source's own diff is the standard to match.
57 want, errOut, code := src.ssh(t, key, "", "mr", "diff", "alice/tool", "1")
58 if code != 0 || !strings.Contains(want, "func F()") {
59 t.Fatalf("source diff: %s\n%s", errOut, want)
60 }
61
62 bundle, errOut, code := src.ssh(t, key, "", "account", "export")
63 if code != 0 {
64 t.Fatalf("export: %s", errOut)
65 }
66 if !strings.Contains(bundle, `"head_sha"`) {
67 t.Fatalf("bundle carries no head_sha:\n%s", bundle)
68 }
69 if _, errOut, code := dst.ssh(t, key, bundle, "account", "import-bundle"); code != 0 {
70 t.Fatalf("import: %s", errOut)
71 }
72
73 // Git data moves separately, which is the order a real migration runs
74 // in: the bundle first, the push after.
75 // Only main: the feature branch is gone, exactly as at the source.
76 // The merge commit carries the head's objects, so they arrive anyway.
77 denv := dst.gitEnv(key)
78 mustGit(t, dir, denv, "remote", "add", "dst", dst.sshURL("alice/tool"))
79 mustGit(t, dir, denv, "fetch", "-q", "origin", "main")
80 mustGit(t, dir, denv, "push", "-q", "dst", "refs/remotes/origin/main:refs/heads/main")
81
82 // Re-importing the same bundle is how the head ref gets set once the
83 // objects are present; the merge request itself is already there.
84 out, errOut, code := dst.ssh(t, key, bundle, "account", "import-bundle")
85 if code != 0 {
86 t.Fatalf("re-import: %s", errOut)
87 }
88 if !strings.Contains(out, "already present") {
89 t.Fatalf("re-import did not skip what it had: %s", out)
90 }
91
92 show, _, _ := dst.ssh(t, key, "", "mr", "show", "alice/tool", "1", "--json")
93 got, errOut, code := dst.ssh(t, key, "", "mr", "diff", "alice/tool", "1")
94 if code != 0 {
95 t.Fatalf("migrated MR has no diff: %s\n%s\nmr show: %s", errOut, got, show)
96 }
97 if !strings.Contains(got, "func F()") {
98 t.Fatalf("migrated diff does not match the source:\nwant to contain func F()\ngot:\n%s", got)
99 }
100}