e2e/mrrebase_test.go

v1.28.1
gitbay/e2e/mrrebase_test.go history · blame · raw

145 lines · 6096 bytes

  1package e2e
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11// `mr rebase` replays a merge request's branch onto its target and
 12// re-pushes it, which is what makes a fast-forward merge possible again
 13// after the target has moved. The git work is local, so the replayed
 14// commits are signed by whatever the user's git config signs with and the
 15// server is never asked to vouch for a commit it did not receive already
 16// signed (#175).
 17func TestCLIMRRebase(t *testing.T) {
 18	inst := startInstance(t)
 19	aliceKey := inst.newKey(t, "alice")
 20	inst.admin(t, "admin", "user", "create", "alice",
 21		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 22
 23	c := &cli{bin: buildGitbayCLI(t), configDir: t.TempDir(), inst: inst, key: aliceKey}
 24	c.must(t, "", "", "remote", "add", "test", "127.0.0.1",
 25		"--port", fmt.Sprint(inst.port),
 26		"--ssh-option", "-i", "--ssh-option", aliceKey,
 27		"--ssh-option", "-oIdentitiesOnly=yes",
 28		"--ssh-option", "-oStrictHostKeyChecking=no",
 29		"--ssh-option", "-oUserKnownHostsFile="+filepath.Join(inst.sshDir, "kh"),
 30		"--ssh-option", "-oBatchMode=yes",
 31		"--default")
 32	c.must(t, "", "", "repo", "create", "alice/app")
 33
 34	env := inst.gitEnv(aliceKey)
 35	work := t.TempDir()
 36	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
 37	dir := filepath.Join(work, "w")
 38	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
 39	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 40	mustGit(t, dir, env, "add", ".")
 41	mustGit(t, dir, env, "commit", "-q", "-m", "base")
 42	mustGit(t, dir, env, "push", "-q", "origin", "main")
 43
 44	// A branch off main, and a merge request for it.
 45	mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
 46	os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b\n"), 0o644)
 47	mustGit(t, dir, env, "add", ".")
 48	mustGit(t, dir, env, "commit", "-q", "-m", "the change")
 49	mustGit(t, dir, env, "push", "-q", "origin", "feat")
 50	c.must(t, dir, "", "mr", "create", "alice/app",
 51		"--source", "feat", "--target", "main", "--title", "change")
 52
 53	// main moves on, so feat is no longer a fast-forward.
 54	mustGit(t, dir, env, "checkout", "-q", "main")
 55	os.WriteFile(filepath.Join(dir, "c.txt"), []byte("c\n"), 0o644)
 56	mustGit(t, dir, env, "add", ".")
 57	mustGit(t, dir, env, "commit", "-q", "-m", "moved on")
 58	mustGit(t, dir, env, "push", "-q", "origin", "main")
 59	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "1", "--strategy", "ff"); code == 0 {
 60		t.Fatal("fast-forward merged a diverged branch")
 61	} else if !strings.Contains(errOut, "fast-forward not possible") {
 62		t.Fatalf("unexpected refusal: %s", errOut)
 63	}
 64
 65	// Rebase, and the same merge now lands.
 66	out, errOut, code := c.run(t, dir, "", "mr", "rebase", "1")
 67	if code != 0 {
 68		t.Fatalf("mr rebase: exit %d\n%s\n%s", code, out, errOut)
 69	}
 70	if !strings.Contains(out, "gitbay mr merge 1") {
 71		t.Errorf("rebase does not name the next step: %s", out)
 72	}
 73	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "1", "--strategy", "ff"); code != 0 {
 74		t.Fatalf("fast-forward still refused after a rebase: %s", errOut)
 75	}
 76
 77	// The rebase moved the branch rather than merging main into it: the
 78	// change is one commit on top of what main had.
 79	out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/app", "1", "--json")
 80	if !strings.Contains(out, `"state":"merged"`) {
 81		t.Fatalf("merge request not merged:\n%s", out)
 82	}
 83}
 84
 85// The guards: a dirty tree, and a source in a fork this clone cannot push.
 86func TestCLIMRRebaseRefusals(t *testing.T) {
 87	inst := startInstance(t)
 88	aliceKey := inst.newKey(t, "alice")
 89	bobKey := inst.newKey(t, "bob")
 90	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 91	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 92
 93	c := &cli{bin: buildGitbayCLI(t), configDir: t.TempDir(), inst: inst, key: aliceKey}
 94	c.must(t, "", "", "remote", "add", "test", "127.0.0.1",
 95		"--port", fmt.Sprint(inst.port),
 96		"--ssh-option", "-i", "--ssh-option", aliceKey,
 97		"--ssh-option", "-oIdentitiesOnly=yes",
 98		"--ssh-option", "-oStrictHostKeyChecking=no",
 99		"--ssh-option", "-oUserKnownHostsFile="+filepath.Join(inst.sshDir, "kh"),
100		"--ssh-option", "-oBatchMode=yes",
101		"--default")
102	c.must(t, "", "", "repo", "create", "alice/app")
103
104	env := inst.gitEnv(aliceKey)
105	work := t.TempDir()
106	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
107	dir := filepath.Join(work, "w")
108	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
109	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
110	mustGit(t, dir, env, "add", ".")
111	mustGit(t, dir, env, "commit", "-q", "-m", "base")
112	mustGit(t, dir, env, "push", "-q", "origin", "main")
113
114	// bob forks, pushes, and opens a merge request from the fork.
115	inst.ssh(t, bobKey, "", "repo", "fork", "alice/app")
116	benv := inst.gitEnv(bobKey)
117	bwork := t.TempDir()
118	mustGit(t, bwork, benv, "clone", inst.sshURL("bob/app"), "w")
119	bdir := filepath.Join(bwork, "w")
120	mustGit(t, bdir, benv, "checkout", "-q", "-b", "feat")
121	os.WriteFile(filepath.Join(bdir, "b.txt"), []byte("b\n"), 0o644)
122	mustGit(t, bdir, benv, "add", ".")
123	mustGit(t, bdir, benv, "commit", "-q", "-m", "from the fork")
124	mustGit(t, bdir, benv, "push", "-q", "origin", "feat")
125	inst.ssh(t, bobKey, "", "mr", "create", "alice/app",
126		"--source", "bob/app:feat", "--target", "main", "--title", "forked")
127
128	// Alice's clone of the target cannot rebase a branch that lives in
129	// bob's fork, and says which repository to do it in.
130	_, errOut, code := c.run(t, dir, "", "mr", "rebase", "1")
131	if code == 0 {
132		t.Fatal("rebased a fork's branch from the target's clone")
133	}
134	if !strings.Contains(errOut, "bob/app:feat") {
135		t.Errorf("refusal does not name the fork: %s", errOut)
136	}
137
138	// A dirty tree is refused before any round trip, so the merge request
139	// number never has to be valid for this one.
140	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("edited\n"), 0o644)
141	if _, errOut, code := c.run(t, dir, "", "mr", "rebase", "1"); code == 0 ||
142		!strings.Contains(errOut, "uncommitted changes") {
143		t.Errorf("dirty tree not refused: exit %d %s", code, errOut)
144	}
145}