carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package changelog
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "reflect"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/docsite"
12)
13
14// scratchRepo builds a small history with every shape main has had:
15//
16// v0.1.0: a root commit adding docs/site/cli.md
17// a direct commit
18// an Amadan squash of branch "feat/x", whose own two commits
19// survive at refs/amadan/closed/feat/x
20// a GitHub merge of branch "gh-branch"
21// v0.2.0
22type scratch struct {
23 t *testing.T
24 dir string
25 n int
26}
27
28func (s *scratch) git(env []string, args ...string) string {
29 s.t.Helper()
30 cmd := exec.Command("git", append([]string{"-C", s.dir}, args...)...)
31 cmd.Env = append(os.Environ(),
32 "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null",
33 "GIT_AUTHOR_NAME=Paul Campbell", "GIT_AUTHOR_EMAIL=paul@tito.io",
34 "GIT_COMMITTER_NAME=Paul Campbell", "GIT_COMMITTER_EMAIL=paul@tito.io")
35 cmd.Env = append(cmd.Env, env...)
36 out, err := cmd.CombinedOutput()
37 if err != nil {
38 s.t.Fatalf("git %v: %v\n%s", args, err, out)
39 }
40 return strings.TrimSpace(string(out))
41}
42
43func (s *scratch) commit(author []string, msg, file, body string) string {
44 s.t.Helper()
45 s.n++
46 date := []string{
47 "GIT_AUTHOR_DATE=2026-09-0" + string(rune('0'+s.n)) + "T12:00:00Z",
48 "GIT_COMMITTER_DATE=2026-09-0" + string(rune('0'+s.n)) + "T12:00:00Z",
49 }
50 if err := os.MkdirAll(filepath.Dir(filepath.Join(s.dir, file)), 0o755); err != nil {
51 s.t.Fatal(err)
52 }
53 if err := os.WriteFile(filepath.Join(s.dir, file), []byte(body), 0o644); err != nil {
54 s.t.Fatal(err)
55 }
56 s.git(nil, "add", file)
57 s.git(append(author, date...), "commit", "-q", "-m", msg)
58 return s.git(nil, "rev-parse", "HEAD")
59}
60
61var asClaude = []string{"GIT_AUTHOR_NAME=claude", "GIT_AUTHOR_EMAIL=claude~paul@keymail.dev"}
62
63func newScratch(t *testing.T) *scratch {
64 s := &scratch{t: t, dir: t.TempDir()}
65 s.git(nil, "init", "-q", "-b", "main")
66 s.commit(nil, "root", "docs/site/cli.md", "# The CLI\n\n### carlos db\n\ntext\n")
67 s.git(nil, "tag", "-a", "v0.1.0", "-m", "carlos v0.1.0")
68 s.commit(nil, "a direct commit\n\nCo-Authored-By: Claude <noreply@anthropic.com>", "a.txt", "a")
69
70 s.git(nil, "checkout", "-q", "-b", "feat/x")
71 s.commit(asClaude, "x one", "x.txt", "1")
72 s.commit(asClaude, "x two (#7)", "docs/site/cli.md", "# The CLI\n\n### carlos db\n\ntext\nmore\n")
73 s.git(nil, "update-ref", "refs/amadan/closed/feat/x", "HEAD")
74 s.git(nil, "checkout", "-q", "main")
75 s.git(nil, "merge", "-q", "--squash", "feat/x")
76 s.git(append(asClaude, "GIT_COMMITTER_DATE=2026-09-08T12:00:00Z", "GIT_AUTHOR_DATE=2026-09-08T12:00:00Z"),
77 "commit", "-q", "-m", "x: the feature\n\nSquashed from feat/x into main.")
78
79 s.git(nil, "checkout", "-q", "-b", "gh-branch")
80 s.commit([]string{"GIT_AUTHOR_NAME=Bill Horsman", "GIT_AUTHOR_EMAIL=bill@keymail.dev"}, "gh work", "g.txt", "g")
81 s.git(nil, "checkout", "-q", "main")
82 s.git([]string{"GIT_AUTHOR_NAME=GitHub", "GIT_AUTHOR_EMAIL=noreply@github.com", "GIT_COMMITTER_DATE=2026-09-09T12:00:00Z"},
83 "merge", "-q", "--no-ff", "gh-branch", "-m", "Merge pull request #3 from carlosframework/gh-branch")
84 s.git(nil, "tag", "v0.2.0")
85 return s
86}
87
88func TestExecGitEndToEnd(t *testing.T) {
89 s := newScratch(t)
90 g, err := NewExecGit(s.dir)
91 if err != nil {
92 t.Fatal(err)
93 }
94 tags, err := g.Tags()
95 if err != nil || len(tags) != 2 {
96 t.Fatalf("tags = %+v, %v", tags, err)
97 }
98 if _, _, ok := g.Tombstone("feat/x"); !ok {
99 t.Fatal("tombstone feat/x not found")
100 }
101
102 rel := fakeReleases{
103 "v0.1.0": "First release.\n",
104 "v0.2.0": "carlos v0.2.0 (platform x, 2026-09-09)\n\n- **The feature.** `carlos db pull`.\n",
105 }
106 disc := &fakeDiscussions{byID: map[int]Discussion{7: {ID: 7, Title: "seven", State: "open", Created: 1}}}
107 site := testSite(t)
108 site.BySlug["cli"] = docsite.Parse("cli", "# The CLI\n\n### carlos db\n\ntext\nmore\n")
109 c, err := Build(Sources{g, rel, disc}, testPeople, Links{"v0.2.0": {"The feature.": {"feat/x"}}}, site)
110 if err != nil {
111 t.Fatal(err)
112 }
113 if len(c.Releases) != 2 || c.Releases[0].Tag != "v0.2.0" {
114 t.Fatalf("releases = %+v", c.Releases)
115 }
116 ch := c.Releases[0].Changes
117 if len(ch) != 3 {
118 t.Fatalf("v0.2.0 changes = %+v", ch)
119 }
120 merge, squash, direct := ch[0], ch[1], ch[2]
121
122 if merge.Branch != "gh-branch" || merge.BranchURL != "" || merge.Commits != 1 || merge.Subject != "gh work" ||
123 !reflect.DeepEqual(merge.Credit, []Credit{{Person: "Bill Horsman"}}) {
124 t.Errorf("merge = %+v", merge)
125 }
126 if squash.Branch != "feat/x" || squash.BranchURL != RepoURL+"/-/branch/feat%2Fx" || squash.Commits != 2 {
127 t.Errorf("squash = %+v", squash)
128 }
129 if !reflect.DeepEqual(squash.Credit, []Credit{{Person: "Paul Campbell", Agents: []string{"claude"}}}) {
130 t.Errorf("squash credit = %+v", squash.Credit)
131 }
132 if len(squash.Discussions) != 1 || squash.Discussions[0].ID != 7 {
133 t.Errorf("squash discussions = %+v", squash.Discussions)
134 }
135 if !reflect.DeepEqual(squash.Docs, []string{"/docs/cli#carlos-db"}) {
136 t.Errorf("squash docs = %v", squash.Docs)
137 }
138 if direct.Branch != "" || !reflect.DeepEqual(direct.Credit, []Credit{{Person: "Paul Campbell", Agents: []string{"claude"}}}) {
139 t.Errorf("direct = %+v", direct)
140 }
141
142 first := c.Releases[1]
143 if first.Tag != "v0.1.0" || !first.Published || len(first.Changes) != 1 || first.Changes[0].Subject != "root" {
144 t.Fatalf("v0.1.0 = %+v", first)
145 }
146 if !reflect.DeepEqual(first.Changes[0].Docs, []string{"/docs/cli#carlos-db"}) {
147 t.Errorf("root docs = %v", first.Changes[0].Docs)
148 }
149}
150