package gittest import ( "strings" "testing" "time" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text" ) func TestWriteCommit(t *testing.T) { cfg, _, repoPath := setup(t) treeEntryA := TreeEntry{Path: "file", Mode: "100644", Content: "something"} treeA := WriteTree(t, cfg, repoPath, []TreeEntry{treeEntryA}) treeB := WriteTree(t, cfg, repoPath, []TreeEntry{ {Path: "file", Mode: "100644", Content: "changed"}, }) commitA := WriteCommit(t, cfg, repoPath, WithTree(treeA)) commitB := WriteCommit(t, cfg, repoPath, WithTree(treeB)) for _, tc := range []struct { desc string opts []WriteCommitOption expectedCommit string expectedTreeEntries []TreeEntry expectedRevUpdate git.Revision }{ { desc: "no options", expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), }, { desc: "with commit message", opts: []WriteCommitOption{ WithMessage("my custom message\n\nfoobar\n"), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "my custom message", "", "foobar", }, "\n"), }, { desc: "with author", opts: []WriteCommitOption{ WithAuthorName("John Doe"), WithAuthorDate(time.Date(2005, 4, 7, 15, 13, 13, 0, time.FixedZone("UTC-7", -7*60*60))), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author John Doe 1112911993 -0700", "committer " + DefaultCommitterSignature, "", "message", }, "\n"), }, { desc: "with committer", opts: []WriteCommitOption{ WithCommitterName("John Doe"), WithCommitterDate(time.Date(2005, 4, 7, 15, 13, 13, 0, time.FixedZone("UTC-7", -7*60*60))), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer John Doe 1112911993 -0700", "", "message", }, "\n"), }, { desc: "with no parents", opts: []WriteCommitOption{ WithParents(), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), }, { desc: "with multiple parents", opts: []WriteCommitOption{ WithParents(commitA, commitB), }, expectedCommit: strings.Join([]string{ "tree " + treeA.String(), "parent " + commitA.String(), "parent " + commitB.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), }, { desc: "with branch", opts: []WriteCommitOption{ WithBranch("foo"), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), expectedRevUpdate: "refs/heads/foo", }, { desc: "with reference", opts: []WriteCommitOption{ WithReference("refs/custom/namespace"), }, expectedCommit: strings.Join([]string{ "tree " + DefaultObjectHash.EmptyTreeOID.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), expectedRevUpdate: "refs/custom/namespace", }, { desc: "with tree entry", opts: []WriteCommitOption{ WithTreeEntries(treeEntryA), }, expectedCommit: strings.Join([]string{ "tree " + treeA.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), expectedTreeEntries: []TreeEntry{treeEntryA}, }, { desc: "with tree", opts: []WriteCommitOption{ WithTree(treeA), }, expectedCommit: strings.Join([]string{ "tree " + treeA.String(), "author " + DefaultCommitterSignature, "committer " + DefaultCommitterSignature, "", "message", }, "\n"), expectedTreeEntries: []TreeEntry{ { Content: "something", Mode: "100644", Path: "file", }, }, }, } { t.Run(tc.desc, func(t *testing.T) { oid := WriteCommit(t, cfg, repoPath, tc.opts...) commit := Exec(t, cfg, "-C", repoPath, "cat-file", "-p", oid.String()) require.Equal(t, tc.expectedCommit, text.ChompBytes(commit)) if tc.expectedTreeEntries != nil { RequireTree(t, cfg, repoPath, oid.String(), tc.expectedTreeEntries) } if tc.expectedRevUpdate != "" { updatedOID := Exec(t, cfg, "-C", repoPath, "rev-parse", tc.expectedRevUpdate.String()) require.Equal(t, oid, git.ObjectID(text.ChompBytes(updatedOID))) } }) } t.Run("empty tree gets written", func(t *testing.T) { cfg, _, repoPath := setup(t) // Write a commit with no tree entries and verify that the repository is consistent. // In the past we didn't write empty trees into the repository, which led to // corruption. WriteCommit(t, cfg, repoPath, WithBranch("main")) Exec(t, cfg, "-C", repoPath, "fsck") RequireObjectExists(t, cfg, repoPath, DefaultObjectHash.EmptyTreeOID) }) }