From 91b45881f8c1fff1d1846b2c68294e73535a4741 Mon Sep 17 00:00:00 2001 From: Saquib Mian Date: Fri, 10 Nov 2023 19:14:46 -0500 Subject: [PATCH] Remove duplicated code for scaffolding git repository (#2559) --- private/buf/bufsync/backfill_tags_test.go | 24 +--- private/buf/bufsync/bufsync_test.go | 70 --------- private/buf/bufsync/commits_to_sync_test.go | 54 +++---- private/buf/bufsync/prepare_sync_test.go | 22 ++- private/pkg/git/gittest/gittest.go | 136 ++++++------------ private/pkg/git/gittest/repository.go | 110 ++++++++++++++ private/pkg/git/repository_test.go | 119 +++++++++++++-- .../pkg/storage/storagegit/storagegit_test.go | 14 ++ 8 files changed, 313 insertions(+), 236 deletions(-) create mode 100644 private/pkg/git/gittest/repository.go diff --git a/private/buf/bufsync/backfill_tags_test.go b/private/buf/bufsync/backfill_tags_test.go index be9c2d5aac..4163861823 100644 --- a/private/buf/bufsync/backfill_tags_test.go +++ b/private/buf/bufsync/backfill_tags_test.go @@ -22,8 +22,8 @@ import ( "github.com/bufbuild/buf/private/buf/bufsync" "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduleref" - "github.com/bufbuild/buf/private/pkg/command" "github.com/bufbuild/buf/private/pkg/git" + "github.com/bufbuild/buf/private/pkg/git/gittest" "github.com/bufbuild/buf/private/pkg/storage/storagegit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,10 +33,10 @@ import ( func TestBackfilltags(t *testing.T) { t.Parallel() const defaultBranchName = "main" - repo, repoDir := scaffoldGitRepository(t, defaultBranchName) + repo := gittest.ScaffoldGitRepository(t) moduleIdentityInHEAD, err := bufmoduleref.NewModuleIdentity("buf.build", "acme", "foo") require.NoError(t, err) - prepareGitRepoBackfillTags(t, repoDir, moduleIdentityInHEAD) + prepareGitRepoBackfillTags(t, repo, moduleIdentityInHEAD) mockHandler := newMockSyncHandler() // prepare the top 5 commits as syncable commits, mark the rest as if they were already synced var ( @@ -95,30 +95,20 @@ func TestBackfilltags(t *testing.T) { // prepareGitRepoBackfillTags adds 20 commits and tags in the default branch, one tag per commit. It // waits 1s between commit 5 and 6 to be easily used as the lookback commit limit time. -func prepareGitRepoBackfillTags(t *testing.T, repoDir string, moduleIdentity bufmoduleref.ModuleIdentity) { - runner := command.NewRunner() +func prepareGitRepoBackfillTags(t *testing.T, repo gittest.Repository, moduleIdentity bufmoduleref.ModuleIdentity) { var commitsCounter int doEmptyCommitAndTag := func(numOfCommits int) { for i := 0; i < numOfCommits; i++ { commitsCounter++ - runInDir( - t, runner, repoDir, - "git", "commit", "--allow-empty", - "-m", fmt.Sprintf("commit %d", commitsCounter), - ) - runInDir( - t, runner, repoDir, - "git", "tag", fmt.Sprintf("tag-%d", commitsCounter), - ) + repo.Commit(t, fmt.Sprintf("commit %d", commitsCounter), nil) + repo.Tag(t, fmt.Sprintf("tag-%d", commitsCounter), "") } } // write the base module in the root - writeFiles(t, repoDir, map[string]string{ + repo.Commit(t, "commit 0", map[string]string{ "buf.yaml": fmt.Sprintf("version: v1\nname: %s\n", moduleIdentity.IdentityString()), "foo/v1/foo.proto": "syntax = \"proto3\";\n\npackage foo.v1;\n\nmessage Foo {}\n", }) - runInDir(t, runner, repoDir, "git", "add", ".") - runInDir(t, runner, repoDir, "git", "commit", "-m", "commit 0") // commit and tag doEmptyCommitAndTag(5) time.Sleep(1 * time.Second) diff --git a/private/buf/bufsync/bufsync_test.go b/private/buf/bufsync/bufsync_test.go index 9702dd69a5..61a05e46be 100644 --- a/private/buf/bufsync/bufsync_test.go +++ b/private/buf/bufsync/bufsync_test.go @@ -15,86 +15,16 @@ package bufsync_test import ( - "bytes" "context" "errors" - "io" - "os" - "path" - "path/filepath" - "strings" - "testing" "time" "github.com/bufbuild/buf/private/buf/bufsync" "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduleref" - "github.com/bufbuild/buf/private/pkg/command" "github.com/bufbuild/buf/private/pkg/git" - "github.com/stretchr/testify/require" "golang.org/x/exp/slices" ) -// scaffoldGitRepository returns an initialized git repository with a single commit, and returns the -// repository and its directory. -func scaffoldGitRepository(t *testing.T, defaultBranchName string) (git.Repository, string) { - runner := command.NewRunner() - repoDir := scaffoldGitRepositoryDir(t, runner, defaultBranchName) - dotGitPath := path.Join(repoDir, git.DotGitDir) - repo, err := git.OpenRepository( - context.Background(), - dotGitPath, - runner, - git.OpenRepositoryWithDefaultBranch(defaultBranchName), - ) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, repo.Close()) - }) - return repo, repoDir -} - -// scaffoldGitRepositoryDir prepares a git repository with an initial README, and a single commit. -// It returns the directory where the local git repo is. -func scaffoldGitRepositoryDir(t *testing.T, runner command.Runner, defaultBranchName string) string { - repoDir := t.TempDir() - - // setup repo - runInDir(t, runner, repoDir, "git", "init", "--initial-branch", defaultBranchName) - runInDir(t, runner, repoDir, "git", "config", "user.name", "Buf TestBot") - runInDir(t, runner, repoDir, "git", "config", "user.email", "testbot@buf.build") - - // write and commit a README file - writeFiles(t, repoDir, map[string]string{"README.md": "This is a scaffold repository.\n"}) - runInDir(t, runner, repoDir, "git", "add", ".") - runInDir(t, runner, repoDir, "git", "commit", "-m", "Write README") - - return repoDir -} - -func runInDir(t *testing.T, runner command.Runner, dir string, cmd string, args ...string) { - stderr := bytes.NewBuffer(nil) - err := runner.Run( - context.Background(), - cmd, - command.RunWithArgs(args...), - command.RunWithDir(dir), - command.RunWithStderr(stderr), - ) - if err != nil { - t.Logf("run %q", strings.Join(append([]string{cmd}, args...), " ")) - _, err := io.Copy(os.Stderr, stderr) - require.NoError(t, err) - } - require.NoError(t, err) -} - -func writeFiles(t *testing.T, directoryPath string, pathToContents map[string]string) { - for path, contents := range pathToContents { - require.NoError(t, os.MkdirAll(filepath.Join(directoryPath, filepath.Dir(path)), 0700)) - require.NoError(t, os.WriteFile(filepath.Join(directoryPath, path), []byte(contents), 0600)) - } -} - type mockClock struct { now time.Time } diff --git a/private/buf/bufsync/commits_to_sync_test.go b/private/buf/bufsync/commits_to_sync_test.go index fc6689d297..af212df051 100644 --- a/private/buf/bufsync/commits_to_sync_test.go +++ b/private/buf/bufsync/commits_to_sync_test.go @@ -21,7 +21,7 @@ import ( "github.com/bufbuild/buf/private/buf/bufsync" "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduleref" - "github.com/bufbuild/buf/private/pkg/command" + "github.com/bufbuild/buf/private/pkg/git/gittest" "github.com/bufbuild/buf/private/pkg/storage/storagegit" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -33,10 +33,8 @@ func TestCommitsToSyncWithNoPreviousSyncPoints(t *testing.T) { require.NoError(t, err) moduleIdentityOverride, err := bufmoduleref.NewModuleIdentity("buf.build", "acme", "bar") require.NoError(t, err) - const defaultBranchName = "main" - repo, repoDir := scaffoldGitRepository(t, defaultBranchName) - runner := command.NewRunner() - prepareGitRepoSyncWithNoPreviousSyncPoints(t, runner, repoDir, moduleIdentityInHEAD, defaultBranchName) + repo := gittest.ScaffoldGitRepository(t) + prepareGitRepoSyncWithNoPreviousSyncPoints(t, repo, moduleIdentityInHEAD, gittest.DefaultBranch) type testCase struct { name string branch string @@ -45,7 +43,7 @@ func TestCommitsToSyncWithNoPreviousSyncPoints(t *testing.T) { testCases := []testCase{ { name: "when_main", - branch: "main", + branch: gittest.DefaultBranch, expectedCommits: 4, // doesn't include initial scaffolding empty commit }, { @@ -70,7 +68,7 @@ func TestCommitsToSyncWithNoPreviousSyncPoints(t *testing.T) { func(tc testCase) { t.Run(fmt.Sprintf("%s/override_%t", tc.name, withOverride), func(t *testing.T) { // check out the branch to sync - runInDir(t, runner, repoDir, "git", "checkout", tc.branch) + repo.Checkout(t, tc.branch) const moduleDir = "." var opts []bufsync.SyncerOption if withOverride { @@ -104,43 +102,35 @@ func TestCommitsToSyncWithNoPreviousSyncPoints(t *testing.T) { // | └o (baz) func prepareGitRepoSyncWithNoPreviousSyncPoints( t *testing.T, - runner command.Runner, - repoDir string, + repo gittest.Repository, moduleIdentity bufmoduleref.ModuleIdentity, defaultBranchName string, ) { var allBranches = []string{defaultBranchName, "foo", "bar", "baz"} var commitsCounter int - doEmptyCommit := func(numOfCommits int) { + doEmptyCommits := func(numOfCommits int) { for i := 0; i < numOfCommits; i++ { commitsCounter++ - runInDir( - t, runner, repoDir, - "git", "commit", "--allow-empty", - "-m", fmt.Sprintf("commit %d", commitsCounter), - ) + repo.Commit(t, fmt.Sprintf("commit %d", commitsCounter), nil) } } - // write the base module in the root - writeFiles(t, repoDir, map[string]string{ + repo.Commit(t, "commit 0", map[string]string{ "buf.yaml": fmt.Sprintf("version: v1\nname: %s\n", moduleIdentity.IdentityString()), }) - runInDir(t, runner, repoDir, "git", "add", ".") - runInDir(t, runner, repoDir, "git", "commit", "-m", "commit 0") - doEmptyCommit(1) - runInDir(t, runner, repoDir, "git", "checkout", "-b", allBranches[1]) - doEmptyCommit(2) - runInDir(t, runner, repoDir, "git", "checkout", defaultBranchName) - doEmptyCommit(1) - runInDir(t, runner, repoDir, "git", "checkout", "-b", allBranches[2]) - doEmptyCommit(1) - runInDir(t, runner, repoDir, "git", "checkout", "-b", allBranches[3]) - doEmptyCommit(1) - runInDir(t, runner, repoDir, "git", "checkout", allBranches[2]) - doEmptyCommit(1) - runInDir(t, runner, repoDir, "git", "checkout", defaultBranchName) - doEmptyCommit(1) + doEmptyCommits(1) + repo.CheckoutB(t, allBranches[1]) + doEmptyCommits(2) + repo.Checkout(t, defaultBranchName) + doEmptyCommits(1) + repo.CheckoutB(t, allBranches[2]) + doEmptyCommits(1) + repo.CheckoutB(t, allBranches[3]) + doEmptyCommits(1) + repo.Checkout(t, allBranches[2]) + doEmptyCommits(1) + repo.Checkout(t, defaultBranchName) + doEmptyCommits(1) } diff --git a/private/buf/bufsync/prepare_sync_test.go b/private/buf/bufsync/prepare_sync_test.go index 110438978a..48cd809922 100644 --- a/private/buf/bufsync/prepare_sync_test.go +++ b/private/buf/bufsync/prepare_sync_test.go @@ -21,7 +21,7 @@ import ( "github.com/bufbuild/buf/private/buf/bufsync" "github.com/bufbuild/buf/private/bufpkg/bufmodule/bufmoduleref" - "github.com/bufbuild/buf/private/pkg/command" + "github.com/bufbuild/buf/private/pkg/git/gittest" "github.com/bufbuild/buf/private/pkg/storage/storagegit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -70,9 +70,8 @@ func TestPrepareSyncDuplicateIdentities(t *testing.T) { func(tc testCase) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - const defaultBranchName = "main" - repo, repoDir := scaffoldGitRepository(t, defaultBranchName) - prepareGitRepoMultiModule(t, repoDir, tc.modulesIdentitiesInHEAD) + repo := gittest.ScaffoldGitRepository(t) + prepareGitRepoMultiModule(t, repo, tc.modulesIdentitiesInHEAD) var moduleDirs []string for moduleDir := range tc.modulesIdentitiesInHEAD { moduleDirs = append(moduleDirs, moduleDir) @@ -93,7 +92,7 @@ func TestPrepareSyncDuplicateIdentities(t *testing.T) { err = syncer.Sync(context.Background()) require.Error(t, err) assert.Contains(t, err.Error(), repeatedIdentity.IdentityString()) - assert.Contains(t, err.Error(), defaultBranchName) + assert.Contains(t, err.Error(), gittest.DefaultBranch) for _, moduleDir := range moduleDirs { assert.Contains(t, err.Error(), moduleDir) } @@ -103,14 +102,11 @@ func TestPrepareSyncDuplicateIdentities(t *testing.T) { } // prepareGitRepoMultiModule commits valid modules to the passed directories and module identities. -func prepareGitRepoMultiModule(t *testing.T, repoDir string, moduleDirsToIdentities map[string]bufmoduleref.ModuleIdentity) { - runner := command.NewRunner() +func prepareGitRepoMultiModule(t *testing.T, repo gittest.Repository, moduleDirsToIdentities map[string]bufmoduleref.ModuleIdentity) { + files := make(map[string]string) for moduleDir, moduleIdentity := range moduleDirsToIdentities { - writeFiles(t, repoDir, map[string]string{ - moduleDir + "/buf.yaml": fmt.Sprintf("version: v1\nname: %s\n", moduleIdentity.IdentityString()), - moduleDir + "/foo/v1/foo.proto": "syntax = \"proto3\";\n\npackage foo.v1;\n\nmessage Foo {}\n", - }) + files[moduleDir+"/buf.yaml"] = fmt.Sprintf("version: v1\nname: %s\n", moduleIdentity.IdentityString()) + files[moduleDir+"/foo/v1/foo.proto"] = "syntax = \"proto3\";\n\npackage foo.v1;\n\nmessage Foo {}\n" } - runInDir(t, runner, repoDir, "git", "add", ".") - runInDir(t, runner, repoDir, "git", "commit", "-m", "commit") + repo.Commit(t, "commit", files) } diff --git a/private/pkg/git/gittest/gittest.go b/private/pkg/git/gittest/gittest.go index 616079033d..29152219cc 100644 --- a/private/pkg/git/gittest/gittest.go +++ b/private/pkg/git/gittest/gittest.go @@ -17,7 +17,7 @@ package gittest import ( "bytes" "context" - "io" + "fmt" "os" "path/filepath" "strings" @@ -34,9 +34,32 @@ const ( DefaultRemote = "origin" ) -func ScaffoldGitRepository(t *testing.T) git.Repository { +type Repository interface { + git.Repository + Commit(t *testing.T, msg string, files map[string]string, opts ...CommitOption) + Checkout(t *testing.T, branch string) + CheckoutB(t *testing.T, branch string) + Tag(t *testing.T, name string, msg string) + Push(t *testing.T) + Merge(t *testing.T, branch string) + PackRefs(t *testing.T) +} + +type commitOpts struct { + executablePaths []string +} + +type CommitOption func(*commitOpts) + +func CommitWithExecutableFile(path string) CommitOption { + return func(opts *commitOpts) { + opts.executablePaths = append(opts.executablePaths, path) + } +} + +func ScaffoldGitRepository(t *testing.T) Repository { runner := command.NewRunner() - dir := scaffoldGitRepository(t, runner) + dir := scaffoldGitRepository(t, runner, DefaultBranch) dotGitPath := filepath.Join(dir, git.DotGitDir) repo, err := git.OpenRepository( context.Background(), @@ -48,29 +71,15 @@ func ScaffoldGitRepository(t *testing.T) git.Repository { t.Cleanup(func() { require.NoError(t, repo.Close()) }) - return repo + testRepo := newRepository( + repo, + dir, + runner, + ) + return testRepo } -// the resulting Git repo looks like so: -// -// . -// ├── proto -// │ ├── acme -// │ │ ├── grocerystore -// │ │ │ └── v1 -// │ │ │ ├── c.proto -// │ │ │ ├── d.proto -// │ │ │ ├── g.proto -// │ │ │ └── h.proto -// │ │ └── petstore -// │ │ └── v1 -// │ │ ├── a.proto -// │ │ ├── b.proto -// │ │ ├── e.proto -// │ │ └── f.proto -// │ └── buf.yaml -// └── randomBinary (+x) -func scaffoldGitRepository(t *testing.T, runner command.Runner) string { +func scaffoldGitRepository(t *testing.T, runner command.Runner, defaultBranch string) string { dir := t.TempDir() // (0) setup local and remote @@ -85,71 +94,11 @@ func scaffoldGitRepository(t *testing.T, runner command.Runner) string { runInDir(t, runner, localDir, "git", "config", "user.email", "testbot@buf.build") runInDir(t, runner, localDir, "git", "remote", "add", DefaultRemote, remoteDir) - // (1) commit in main branch - writeFiles(t, localDir, map[string]string{ - "randomBinary": "some executable", - "proto/buf.yaml": "some buf.yaml", - "proto/acme/petstore/v1/a.proto": "cats", - "proto/acme/petstore/v1/b.proto": "animals", - "proto/acme/grocerystore/v1/c.proto": "toysrus", - "proto/acme/grocerystore/v1/d.proto": "petsrus", - }) - runInDir(t, runner, localDir, "chmod", "+x", "randomBinary") + // (1) initial commit and push + writeFiles(t, localDir, map[string]string{"README.md": "This is a scaffold repository.\n"}) runInDir(t, runner, localDir, "git", "add", ".") runInDir(t, runner, localDir, "git", "commit", "-m", "initial commit") - runInDir(t, runner, localDir, "git", "tag", "release/v1") - runInDir(t, runner, localDir, "git", "push", "--follow-tags", "-u", "-f", "origin", DefaultBranch) - - // (2) branch off main and begin work - runInDir(t, runner, localDir, "git", "checkout", "-b", "buftest/branch1") - writeFiles(t, localDir, map[string]string{ - "proto/acme/petstore/v1/e.proto": "loblaws", - "proto/acme/petstore/v1/f.proto": "merchant of venice", - }) - runInDir(t, runner, localDir, "git", "add", ".") - runInDir(t, runner, localDir, "git", "commit", "-m", "branch1") - runInDir(t, runner, localDir, "git", "tag", "-m", "for testing", "branch/v1") - runInDir(t, runner, localDir, "git", "push", "--follow-tags", "origin", "buftest/branch1") - - // (3) branch off branch and begin work - runInDir(t, runner, localDir, "git", "checkout", "-b", "buftest/branch2") - writeFiles(t, localDir, map[string]string{ - "proto/acme/grocerystore/v1/g.proto": "hamlet", - "proto/acme/grocerystore/v1/h.proto": "bethoven", - }) - runInDir(t, runner, localDir, "git", "add", ".") - runInDir(t, runner, localDir, "git", "commit", "-m", "branch2") - runInDir(t, runner, localDir, "git", "tag", "-m", "for testing", "branch/v2") - runInDir(t, runner, localDir, "git", "push", "--follow-tags", "origin", "buftest/branch2") - - // (4) merge first branch - runInDir(t, runner, localDir, "git", "checkout", DefaultBranch) - runInDir(t, runner, localDir, "git", "merge", "--squash", "buftest/branch1") - runInDir(t, runner, localDir, "git", "commit", "-m", "second commit") - runInDir(t, runner, localDir, "git", "tag", "v2") - runInDir(t, runner, localDir, "git", "push", "--follow-tags") - - // (5) pack some refs - runInDir(t, runner, localDir, "git", "pack-refs", "--all") - runInDir(t, runner, localDir, "git", "repack") - - // (6) merge second branch - runInDir(t, runner, localDir, "git", "checkout", DefaultBranch) - runInDir(t, runner, localDir, "git", "merge", "--squash", "buftest/branch2") - runInDir(t, runner, localDir, "git", "commit", "-m", "third commit") - runInDir(t, runner, localDir, "git", "tag", "v3.0") - runInDir(t, runner, localDir, "git", "push", "--follow-tags") - - // commit a local-only branch - runInDir(t, runner, localDir, "git", "checkout", "-b", "buftest/local-only") - runInDir(t, runner, localDir, "git", "commit", "--allow-empty", "-m", "local commit on local branch") - - // make a local-only commit on top of a pushed branch - runInDir(t, runner, localDir, "git", "checkout", "buftest/branch1") - runInDir(t, runner, localDir, "git", "commit", "--allow-empty", "-m", "local commit on pushed branch") - - // checkout to default branch - runInDir(t, runner, localDir, "git", "checkout", DefaultBranch) + runInDir(t, runner, localDir, "git", "push", "-u", "-f", "origin", defaultBranch) return localDir } @@ -164,11 +113,16 @@ func runInDir(t *testing.T, runner command.Runner, dir string, cmd string, args command.RunWithStderr(stderr), ) if err != nil { - t.Logf("run %q", strings.Join(append([]string{cmd}, args...), " ")) - _, err := io.Copy(os.Stderr, stderr) - require.NoError(t, err) + require.FailNow( + t, + fmt.Sprintf( + "git command failed: %q: %s", + strings.Join(append([]string{cmd}, args...), " "), + err.Error(), + ), + stderr, + ) } - require.NoError(t, err) } func writeFiles(t *testing.T, dir string, files map[string]string) { diff --git a/private/pkg/git/gittest/repository.go b/private/pkg/git/gittest/repository.go new file mode 100644 index 0000000000..ad9951d54f --- /dev/null +++ b/private/pkg/git/gittest/repository.go @@ -0,0 +1,110 @@ +// Copyright 2020-2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gittest + +import ( + "context" + "testing" + + "github.com/bufbuild/buf/private/pkg/command" + "github.com/bufbuild/buf/private/pkg/git" + "github.com/stretchr/testify/require" +) + +type repository struct { + inner git.Repository + repoDir string + runner command.Runner +} + +func newRepository( + inner git.Repository, + repoDir string, + runner command.Runner, +) *repository { + return &repository{ + inner: inner, + repoDir: repoDir, + runner: runner, + } +} + +func (r *repository) Close() error { + return r.inner.Close() +} +func (r *repository) CurrentBranch(ctx context.Context) (string, error) { + return r.inner.CurrentBranch(ctx) +} +func (r *repository) DefaultBranch() string { + return r.inner.DefaultBranch() +} +func (r *repository) ForEachBranch(f func(branch string, headHash git.Hash) error, options ...git.ForEachBranchOption) error { + return r.inner.ForEachBranch(f, options...) +} +func (r *repository) ForEachCommit(f func(commit git.Commit) error, options ...git.ForEachCommitOption) error { + return r.inner.ForEachCommit(f, options...) +} +func (r *repository) ForEachTag(f func(tag string, commitHash git.Hash) error) error { + return r.inner.ForEachTag(f) +} +func (r *repository) HEADCommit(options ...git.HEADCommitOption) (git.Commit, error) { + return r.inner.HEADCommit(options...) +} +func (r *repository) Objects() git.ObjectReader { + return r.inner.Objects() +} +func (r *repository) Checkout(t *testing.T, branch string) { + runInDir(t, r.runner, r.repoDir, "git", "checkout", branch) +} +func (r *repository) CheckoutB(t *testing.T, branch string) { + runInDir(t, r.runner, r.repoDir, "git", "checkout", "-b", branch) +} +func (r *repository) Commit(t *testing.T, msg string, files map[string]string, opts ...CommitOption) { + if len(files) == 0 { + runInDir(t, r.runner, r.repoDir, "git", "commit", "--allow-empty", "-m", msg) + return + } + var options commitOpts + for _, opt := range opts { + opt(&options) + } + writeFiles(t, r.repoDir, files) + for _, path := range options.executablePaths { + runInDir(t, r.runner, r.repoDir, "chmod", "+x", path) + } + runInDir(t, r.runner, r.repoDir, "git", "add", ".") + runInDir(t, r.runner, r.repoDir, "git", "commit", "-m", msg) +} +func (r *repository) Tag(t *testing.T, name string, msg string) { + if msg != "" { + runInDir(t, r.runner, r.repoDir, "git", "tag", "-m", msg, name) + } else { + runInDir(t, r.runner, r.repoDir, "git", "tag", name) + } +} +func (r *repository) Push(t *testing.T) { + currentBranch, err := r.CurrentBranch(context.Background()) + require.NoError(t, err) + runInDir(t, r.runner, r.repoDir, "git", "push", "--follow-tags", "origin", currentBranch) +} +func (r *repository) Merge(t *testing.T, branch string) { + runInDir(t, r.runner, r.repoDir, "git", "merge", "--squash", branch) +} +func (r *repository) PackRefs(t *testing.T) { + runInDir(t, r.runner, r.repoDir, "git", "pack-refs", "--all") + runInDir(t, r.runner, r.repoDir, "git", "repack") +} + +var _ Repository = (*repository)(nil) diff --git a/private/pkg/git/repository_test.go b/private/pkg/git/repository_test.go index 11a4e22750..fafc2f2d4c 100644 --- a/private/pkg/git/repository_test.go +++ b/private/pkg/git/repository_test.go @@ -28,6 +28,7 @@ func TestTags(t *testing.T) { t.Parallel() repo := gittest.ScaffoldGitRepository(t) + writeModuleWithSampleCommits(t, context.Background(), repo) var tags []string err := repo.ForEachTag(func(tag string, commitHash git.Hash) error { tags = append(tags, tag) @@ -36,7 +37,7 @@ func TestTags(t *testing.T) { require.NoError(t, err) switch tag { case "release/v1": - assert.Equal(t, commit.Message(), "initial commit") + assert.Equal(t, commit.Message(), "first commit") case "branch/v1": assert.Equal(t, commit.Message(), "branch1") case "branch/v2": @@ -66,20 +67,23 @@ func TestCommits(t *testing.T) { t.Parallel() repo := gittest.ScaffoldGitRepository(t) + writeModuleWithSampleCommits(t, context.Background(), repo) var commitsByDefault []git.Commit err := repo.ForEachCommit(func(c git.Commit) error { commitsByDefault = append(commitsByDefault, c) // by default we loop from HEAD at the local default branch return nil }) require.NoError(t, err) - require.Len(t, commitsByDefault, 3) + require.Len(t, commitsByDefault, 4) assert.Equal(t, commitsByDefault[0].Message(), "third commit") assert.Contains(t, commitsByDefault[0].Parents(), commitsByDefault[1].Hash()) assert.Equal(t, commitsByDefault[1].Message(), "second commit") assert.Contains(t, commitsByDefault[1].Parents(), commitsByDefault[2].Hash()) - assert.Equal(t, commitsByDefault[2].Message(), "initial commit") - assert.Empty(t, commitsByDefault[2].Parents()) + assert.Equal(t, commitsByDefault[2].Message(), "first commit") + assert.Contains(t, commitsByDefault[2].Parents(), commitsByDefault[3].Hash()) + assert.Equal(t, commitsByDefault[3].Message(), "initial commit") + assert.Empty(t, commitsByDefault[3].Parents()) t.Run("default_behavior", func(t *testing.T) { var commitsFromDefaultBranch []git.Commit @@ -104,12 +108,14 @@ func TestCommits(t *testing.T) { git.ForEachCommitWithHashStartPoint(commitsByDefault[1].Hash().Hex()), ) require.NoError(t, err) - require.Len(t, commitsFromSecond, 2) + require.Len(t, commitsFromSecond, 3) assert.Equal(t, commitsFromSecond[0].Message(), "second commit") assert.Contains(t, commitsFromSecond[0].Parents(), commitsFromSecond[1].Hash()) - assert.Equal(t, commitsFromSecond[1].Message(), "initial commit") - assert.Empty(t, commitsFromSecond[1].Parents()) + assert.Equal(t, commitsFromSecond[1].Message(), "first commit") + assert.Contains(t, commitsFromSecond[1].Parents(), commitsFromSecond[2].Hash()) + assert.Equal(t, commitsFromSecond[2].Message(), "initial commit") + assert.Empty(t, commitsFromSecond[2].Parents()) }) t.Run("branch_starting_point", func(t *testing.T) { @@ -124,14 +130,16 @@ func TestCommits(t *testing.T) { git.ForEachCommitWithBranchStartPoint(branchName), ) require.NoError(t, err) - require.Len(t, commitsFromLocalBranch, 3) + require.Len(t, commitsFromLocalBranch, 4) assert.Equal(t, commitsFromLocalBranch[0].Message(), "local commit on pushed branch") assert.Contains(t, commitsFromLocalBranch[0].Parents(), commitsFromLocalBranch[1].Hash()) assert.Equal(t, commitsFromLocalBranch[1].Message(), "branch1") assert.Contains(t, commitsFromLocalBranch[1].Parents(), commitsFromLocalBranch[2].Hash()) - assert.Equal(t, commitsFromLocalBranch[2].Message(), "initial commit") - assert.Empty(t, commitsFromLocalBranch[2].Parents()) + assert.Equal(t, commitsFromLocalBranch[2].Message(), "first commit") + assert.Contains(t, commitsFromLocalBranch[2].Parents(), commitsFromLocalBranch[3].Hash()) + assert.Equal(t, commitsFromLocalBranch[3].Message(), "initial commit") + assert.Empty(t, commitsFromLocalBranch[3].Parents()) }) t.Run("when_remote", func(t *testing.T) { @@ -147,12 +155,14 @@ func TestCommits(t *testing.T) { ), ) require.NoError(t, err) - require.Len(t, commitsFromRemoteBranch, 2) + require.Len(t, commitsFromRemoteBranch, 3) assert.Equal(t, commitsFromRemoteBranch[0].Message(), "branch1") assert.Contains(t, commitsFromRemoteBranch[0].Parents(), commitsFromRemoteBranch[1].Hash()) - assert.Equal(t, commitsFromRemoteBranch[1].Message(), "initial commit") - assert.Empty(t, commitsFromRemoteBranch[1].Parents()) + assert.Equal(t, commitsFromRemoteBranch[1].Message(), "first commit") + assert.Contains(t, commitsFromRemoteBranch[1].Parents(), commitsFromRemoteBranch[2].Hash()) + assert.Equal(t, commitsFromRemoteBranch[2].Message(), "initial commit") + assert.Empty(t, commitsFromRemoteBranch[2].Parents()) }) }) @@ -236,6 +246,7 @@ func TestForEachBranch(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() repo := gittest.ScaffoldGitRepository(t) + writeModuleWithSampleCommits(t, context.Background(), repo) currentBranch, err := repo.CurrentBranch(context.Background()) require.NoError(t, err) assert.Equal(t, gittest.DefaultBranch, currentBranch) @@ -273,3 +284,85 @@ func TestForEachBranch(t *testing.T) { }(tc) } } + +// the resulting Git repo looks like so: +// +// . +// ├── proto +// │ ├── acme +// │ │ ├── grocerystore +// │ │ │ └── v1 +// │ │ │ ├── c.proto +// │ │ │ ├── d.proto +// │ │ │ ├── g.proto +// │ │ │ └── h.proto +// │ │ └── petstore +// │ │ └── v1 +// │ │ ├── a.proto +// │ │ ├── b.proto +// │ │ ├── e.proto +// │ │ └── f.proto +// │ └── buf.yaml +// └── randomBinary (+x) +func writeModuleWithSampleCommits(t *testing.T, ctx context.Context, repo gittest.Repository) { + currentBranch, err := repo.CurrentBranch(ctx) + require.NoError(t, err) + + // (1) commit in main branch + repo.Commit(t, "first commit", map[string]string{ + "randomBinary": "some executable", + "proto/buf.yaml": "some buf.yaml", + "proto/acme/petstore/v1/a.proto": "cats", + "proto/acme/petstore/v1/b.proto": "animals", + "proto/acme/grocerystore/v1/c.proto": "toysrus", + "proto/acme/grocerystore/v1/d.proto": "petsrus", + }, gittest.CommitWithExecutableFile("randomBinary")) + repo.Tag(t, "release/v1", "") + repo.Push(t) + + // (2) branch off main and begin work + repo.CheckoutB(t, "buftest/branch1") + repo.Commit(t, "branch1", map[string]string{ + "proto/acme/petstore/v1/e.proto": "loblaws", + "proto/acme/petstore/v1/f.proto": "merchant of venice", + }) + repo.Tag(t, "branch/v1", "for testing") + repo.Push(t) + + // (3) branch off branch and begin work + repo.CheckoutB(t, "buftest/branch2") + repo.Commit(t, "branch2", map[string]string{ + "proto/acme/grocerystore/v1/g.proto": "hamlet", + "proto/acme/grocerystore/v1/h.proto": "bethoven", + }) + repo.Tag(t, "branch/v2", "for testing") + repo.Push(t) + + // (4) merge first branch + repo.Checkout(t, currentBranch) + repo.Merge(t, "buftest/branch1") + repo.Commit(t, "second commit", nil) + repo.Tag(t, "v2", "") + repo.Push(t) + + // (5) pack some refs + repo.PackRefs(t) + + // (6) merge second branch + repo.Checkout(t, currentBranch) + repo.Merge(t, "buftest/branch2") + repo.Commit(t, "third commit", nil) + repo.Tag(t, "v3.0", "") + repo.Push(t) + + // commit a local-only branch + repo.CheckoutB(t, "buftest/local-only") + repo.Commit(t, "local commit on local branch", nil) + + // make a local-only commit on top of a pushed branch + repo.Checkout(t, "buftest/branch1") + repo.Commit(t, "local commit on pushed branch", nil) + + // checkout to default branch + repo.Checkout(t, currentBranch) +} diff --git a/private/pkg/storage/storagegit/storagegit_test.go b/private/pkg/storage/storagegit/storagegit_test.go index 4eb3febd3a..cdca41df2b 100644 --- a/private/pkg/storage/storagegit/storagegit_test.go +++ b/private/pkg/storage/storagegit/storagegit_test.go @@ -27,6 +27,18 @@ func TestNewBucketAtTreeHash(t *testing.T) { repo := gittest.ScaffoldGitRepository(t) provider := NewProvider(repo.Objects()) + repo.Commit(t, "first commit", map[string]string{ + "randomBinary": "some executable", + "proto/buf.yaml": "some buf.yaml", + "proto/acme/petstore/v1/a.proto": "cats", + "proto/acme/petstore/v1/b.proto": "animals", + "proto/acme/petstore/v1/e.proto": "loblaws", + "proto/acme/petstore/v1/f.proto": "merchant of venice", + "proto/acme/grocerystore/v1/c.proto": "toysrus", + "proto/acme/grocerystore/v1/d.proto": "petsrus", + "proto/acme/grocerystore/v1/g.proto": "hamlet", + "proto/acme/grocerystore/v1/h.proto": "bethoven", + }, gittest.CommitWithExecutableFile("randomBinary")) headCommit, err := repo.HEADCommit() require.NoError(t, err) require.NotNil(t, headCommit) @@ -37,6 +49,7 @@ func TestNewBucketAtTreeHash(t *testing.T) { t, bucket, "", + "README.md", "proto/acme/grocerystore/v1/c.proto", "proto/acme/grocerystore/v1/d.proto", "proto/acme/grocerystore/v1/g.proto", @@ -60,6 +73,7 @@ func TestNewBucketAtTreeHash(t *testing.T) { bucket, "", map[string]string{ + "README.md": "This is a scaffold repository.\n", "proto/acme/grocerystore/v1/c.proto": "toysrus", "proto/acme/grocerystore/v1/d.proto": "petsrus", "proto/acme/grocerystore/v1/g.proto": "hamlet",