返回 DeepSeek-Reasonix
stage_test.go
根目录 / internal / gitcmd / stage_test.go
1 package gitcmd
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 )
9
10 // A nested repository registered as a gitlink carries its own local filter;
11 // staging the superproject must neither run it nor lose the rest of the tree.
12 func TestStageAllDoesNotEnterNestedRepositories(t *testing.T) {
13 f := newRepoFixture(t, "", map[string]string{"x.txt": "one\n"})
14 sub := filepath.Join(f.dir, "sub")
15 f.plainIn(f.dir, "init", "--quiet", "sub")
16 f.write("sub/f.txt", "a\n")
17 f.write("sub/.gitattributes", "f.txt filter=evil\n")
18 f.plainIn(sub, "add", "-A")
19 f.plainIn(sub, "-c", "user.email=t@t", "-c", "user.name=t", "commit", "--quiet", "-m", "s")
20 f.plainIn(sub, "config", "filter.evil.clean", f.payload())
21 gitlink := strings.TrimSpace(f.plainIn(sub, "rev-parse", "HEAD"))
22 f.plain("update-index", "--add", "--cacheinfo", "160000,"+gitlink+",sub")
23 f.plain("commit", "--quiet", "-m", "sub")
24 f.makeStatDirty("sub/f.txt", "b\n")
25 f.plainIn(f.dir, "init", "--quiet", "nest")
26 f.write("nest/q.txt", "q\n")
27 f.write("x.txt", "two\n")
28 f.write("y.txt", "new\n")
29
30 repo := f.open(f.dir)
31 index := filepath.Join(t.TempDir(), "index")
32 raw, err := os.ReadFile(filepath.Join(f.dir, ".git", "index"))
33 if err != nil {
34 t.Fatal(err)
35 }
36 if err := os.WriteFile(index, raw, 0o644); err != nil {
37 t.Fatal(err)
38 }
39 env := "GIT_INDEX_FILE=" + index
40 if err := repo.StageAll(f.ctx, env); err != nil {
41 t.Fatalf("StageAll: %v", err)
42 }
43 f.assertNotExecuted()
44 cmd := repo.Command(f.ctx, "ls-files", "-s")
45 cmd.Env = append(cmd.Env, env)
46 out, err := cmd.Output()
47 if err != nil {
48 t.Fatal(err)
49 }
50 got := string(out)
51 for _, want := range []string{"160000 " + gitlink + " 0\tsub\n", "\tx.txt\n", "\ty.txt\n"} {
52 if !strings.Contains(got, want) {
53 t.Fatalf("staged index:\n%s\nwant %q", got, want)
54 }
55 }
56 if strings.Contains(got, "nest") {
57 t.Fatalf("staged index:\n%s\nwant the untracked nested repository left out", got)
58 }
59 x := strings.TrimSpace(f.plain("hash-object", "x.txt"))
60 if !strings.Contains(got, x+" 0\tx.txt") {
61 t.Fatalf("staged index:\n%s\nwant x.txt at its working-tree content %s", got, x)
62 }
63 }
64
65 // A user's own submodule.recurse=true must not carry host checkouts and resets
66 // into a submodule, where that submodule's config would name the filter.
67 func TestUserRecurseDoesNotCarryHostCheckoutIntoSubmodules(t *testing.T) {
68 sub := newRepoFixture(t, "s.txt filter=pwn\n", map[string]string{"s.txt": "v1\n"})
69 f := newRepoFixture(t, "", map[string]string{"top.txt": "top\n"})
70 f.plain("-c", "protocol.file.allow=always", "submodule", "--quiet", "add", sub.dir, "sm")
71 f.plain("commit", "--quiet", "-m", "add submodule")
72 f.plain("checkout", "--quiet", "-b", "other")
73 sub.write("s.txt", "v2\n")
74 sub.plain("commit", "--quiet", "-am", "v2")
75 f.plainIn(filepath.Join(f.dir, "sm"), "-c", "protocol.file.allow=always", "fetch", "--quiet")
76 f.plainIn(filepath.Join(f.dir, "sm"), "checkout", "--quiet", "origin/main")
77 f.plain("commit", "--quiet", "-am", "bump")
78 f.plain("checkout", "--quiet", "main")
79 f.plain("submodule", "--quiet", "update")
80 f.appendConfig("modules/sm/config", "[filter \"pwn\"]\n\tsmudge = "+f.payload()+"\n\tclean = "+f.payload()+"\n")
81 global := filepath.Join(t.TempDir(), "gitconfig")
82 if err := os.WriteFile(global, []byte("[submodule]\n\trecurse = true\n"), 0o644); err != nil {
83 t.Fatal(err)
84 }
85 t.Setenv("GIT_CONFIG_GLOBAL", global)
86 repo := f.open(f.dir)
87 for _, args := range [][]string{{"checkout", "--quiet", "other"}, {"reset", "--quiet", "--hard", "main"}} {
88 if out, err := repo.Command(f.ctx, args...).CombinedOutput(); err != nil {
89 t.Fatalf("host %v: %v: %s", args, err, out)
90 }
91 f.assertNotExecuted()
92 }
93 }
94
94 lines GO