e2e/import_test.go

32a5f76e5b270097b63a5bba9a43557cf50ad63d
gitbay/e2e/import_test.go history · blame · raw

148 lines · 6320 bytes

  1package e2e
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7	"regexp"
  8	"strings"
  9	"testing"
 10)
 11
 12func TestRepoImport(t *testing.T) {
 13	t.Parallel()
 14	inst := startInstance(t)
 15	aliceKey := inst.newKey(t, "alice")
 16	inst.admin(t, "admin", "user", "create", "alice",
 17		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 18
 19	// Source repo with a non-"main" default branch, a tag, and two commits.
 20	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/src"); code != 0 {
 21		t.Fatalf("repo create: %s", errOut)
 22	}
 23	work := t.TempDir()
 24	env := inst.gitEnv(aliceKey)
 25	mustGit(t, work, env, "clone", inst.sshURL("alice/src"), "w")
 26	dir := filepath.Join(work, "w")
 27	os.WriteFile(filepath.Join(dir, "code.txt"), []byte("v1\n"), 0o644)
 28	mustGit(t, dir, env, "checkout", "-q", "-b", "trunk")
 29	mustGit(t, dir, env, "add", ".")
 30	mustGit(t, dir, env, "commit", "-q", "-m", "first")
 31	mustGit(t, dir, env, "tag", "v1.0")
 32	mustGit(t, dir, env, "commit", "-q", "--allow-empty", "-m", "second")
 33	mustGit(t, dir, env, "push", "-q", "origin", "trunk", "v1.0")
 34
 35	// Point the source repo's HEAD at trunk so the remote advertises it.
 36	srcBare := filepath.Join(inst.root, "repos", "alice", "src.git")
 37	mustGit(t, srcBare, env, "symbolic-ref", "HEAD", "refs/heads/trunk")
 38
 39	// Import over HTTP from our own instance (public smart HTTP).
 40	httpURL := fmt.Sprintf("http://127.0.0.1:%d/alice/src.git", inst.httpPort)
 41	out, errOut, code := inst.ssh(t, aliceKey, "", "repo", "import", "alice/mirror", "--from", httpURL, "--private")
 42	if code != 0 {
 43		t.Fatalf("import: exit %d\n%s%s", code, out, errOut)
 44	}
 45	if !strings.Contains(out, "imported alice/mirror (private, default trunk)") {
 46		t.Fatalf("import output: %s", out)
 47	}
 48	if !strings.Contains(out, "git data only") {
 49		t.Fatalf("import note missing: %s", out)
 50	}
 51
 52	// Everything came across: both commits, the tag, and the default branch.
 53	logOut, _, code := inst.ssh(t, aliceKey, "", "repo", "log", "alice/mirror")
 54	if code != 0 || !strings.Contains(logOut, "second") || !strings.Contains(logOut, "first") {
 55		t.Fatalf("imported log: %d\n%s", code, logOut)
 56	}
 57	mirrorBare := filepath.Join(inst.root, "repos", "alice", "mirror.git")
 58	tags := mustGit(t, mirrorBare, env, "tag", "--list")
 59	if !strings.Contains(tags, "v1.0") {
 60		t.Fatalf("tag not imported: %q", tags)
 61	}
 62	head := strings.TrimSpace(mustGit(t, mirrorBare, env, "symbolic-ref", "HEAD"))
 63	if head != "refs/heads/trunk" {
 64		t.Fatalf("imported HEAD = %s", head)
 65	}
 66	showOut, _, _ := inst.ssh(t, aliceKey, "", "repo", "show", "alice/mirror")
 67	if !strings.Contains(showOut, "private") || !regexp.MustCompile(`default branch\s+trunk`).MatchString(showOut) {
 68		t.Fatalf("repo show after import: %s", showOut)
 69	}
 70
 71	// The imported repo has working hooks (core.hooksPath was set): a
 72	// protected-branch force-push is refused.
 73	if _, errOut, code = inst.ssh(t, aliceKey, "", "repo", "settings", "protect", "alice/mirror", "trunk"); code != 0 {
 74		t.Fatalf("protect: %s", errOut)
 75	}
 76	mWork := t.TempDir()
 77	mustGit(t, mWork, env, "clone", inst.sshURL("alice/mirror"), "m")
 78	mDir := filepath.Join(mWork, "m")
 79	mustGit(t, mDir, env, "commit", "-q", "--amend", "--allow-empty", "-m", "rewrite")
 80	pushOut, pushCode := gitRun(t, mDir, env, "push", "--force", "origin", "trunk")
 81	if pushCode == 0 || !strings.Contains(pushOut, "force-push refused") {
 82		t.Fatalf("hooks not wired on imported repo:\n%s", pushOut)
 83	}
 84
 85	// Import over git:// too.
 86	if _, errOut, code = inst.ssh(t, aliceKey, "", "repo", "settings", "git-daemon", "alice/src", "on"); code != 0 {
 87		t.Fatalf("git-daemon on: %s", errOut)
 88	}
 89	gitURL := fmt.Sprintf("git://127.0.0.1:%d/alice/src.git", inst.gitPort)
 90	if _, errOut, code = inst.ssh(t, aliceKey, "", "repo", "import", "alice/mirror2", "--from", gitURL); code != 0 {
 91		t.Fatalf("git:// import: %s", errOut)
 92	}
 93
 94	// Org-owned imports: allowed for org admins, refused for non-members.
 95	if _, errOut, code := inst.ssh(t, aliceKey, "", "org", "create", "imports"); code != 0 {
 96		t.Fatalf("org create: %s", errOut)
 97	}
 98	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "import", "imports/mirror", "--from", httpURL); code != 0 {
 99		t.Fatalf("org import: %s", errOut)
100	}
101	if out, _, code := inst.ssh(t, aliceKey, "", "repo", "log", "imports/mirror"); code != 0 || !strings.Contains(out, "first") {
102		t.Fatalf("org import log: %d\n%s", code, out)
103	}
104
105	// Refusals: bad scheme, credentials in URL, existing name, foreign owner.
106	cases := []struct {
107		args []string
108		want string
109	}{
110		{[]string{"repo", "import", "alice/x", "--from", "file:///etc"}, "https://, http://, and git://"},
111		{[]string{"repo", "import", "alice/x", "--from", "https://token@github.com/a/b"}, "--token-stdin"},
112		{[]string{"repo", "import", "alice/mirror", "--from", httpURL}, "already exists"},
113		{[]string{"repo", "import", "bob/x", "--from", httpURL}, "not you and not an organization"},
114	}
115	for _, tc := range cases {
116		_, errOut, code := inst.ssh(t, aliceKey, "", tc.args...)
117		if code == 0 || !strings.Contains(errOut, tc.want) {
118			t.Errorf("%v: exit %d, stderr %q (want %q)", tc.args, code, errOut, tc.want)
119		}
120	}
121
122	// A failed import leaves nothing behind.
123	_, _, code = inst.ssh(t, aliceKey, "", "repo", "import", "alice/gone", "--from",
124		fmt.Sprintf("http://127.0.0.1:%d/alice/nonexistent.git", inst.httpPort))
125	if code == 0 {
126		t.Fatal("import of nonexistent source succeeded")
127	}
128	if _, _, code = inst.ssh(t, aliceKey, "", "repo", "show", "alice/gone"); code != 3 {
129		t.Fatalf("failed import left a repo behind: exit %d, want 3", code)
130	}
131	if _, err := os.Stat(filepath.Join(inst.root, "repos", "alice", "gone.git")); !os.IsNotExist(err) {
132		t.Fatal("failed import left a directory behind")
133	}
134
135	// --token-stdin consumes a token from stdin without leaking it: the
136	// fetch works (token unused by our anonymous endpoint, but the askpass
137	// plumbing must not break it) and the token string appears nowhere in
138	// the repo config.
139	_, errOut, code = inst.ssh(t, aliceKey, "s3cr3t-token\n", "repo", "import", "alice/mirror3",
140		"--from", httpURL, "--token-stdin")
141	if code != 0 {
142		t.Fatalf("token-stdin import: %s", errOut)
143	}
144	cfgRaw, _ := os.ReadFile(filepath.Join(inst.root, "repos", "alice", "mirror3.git", "config"))
145	if strings.Contains(string(cfgRaw), "s3cr3t") {
146		t.Fatal("token leaked into repo config")
147	}
148}