diff --git a/internal/commands/release/create/create.go b/internal/commands/release/create/create.go index 1a687ae03e9db40654ca3c6b0695615bec966573..0848513920159cdc807105936c2f76e7370c18cb 100644 --- a/internal/commands/release/create/create.go +++ b/internal/commands/release/create/create.go @@ -332,7 +332,16 @@ func createRun(opts *options) error { opts.io.LogInfo(color.DotWarnIcon(), "Tag does not exist.") opts.io.LogInfo(color.DotWarnIcon(), "No ref provided. Creating the tag from the latest state of the default branch.") project, err := repo.Project(client) - if err == nil { + if err != nil { + // We are not able to retrieve the project from the API. + // This is most likely because we are running in CI with a CI Job Token + // which does not have access to the Projects API. Thus, let's check if we have access + // to the predefined CI/CD variable CI_DEFAULT_BRANCH and use it instead if available. + if defaultBranch, found := os.LookupEnv("CI_DEFAULT_BRANCH"); found { + opts.io.LogInfof("%s using default branch %q as ref from CI_DEFAULT_BRANCH\n", color.ProgressIcon(), defaultBranch) + opts.ref = defaultBranch + } + } else { opts.io.LogInfof("%s using default branch %q as ref\n", color.ProgressIcon(), project.DefaultBranch) opts.ref = project.DefaultBranch } diff --git a/internal/commands/release/create/create_test.go b/internal/commands/release/create/create_test.go index 1fe2c9a48c99abc5a5c9e866597ab961a88dbdcf..b76661609522a5288cde0422291bdb1fe6a3ca21 100644 --- a/internal/commands/release/create/create_test.go +++ b/internal/commands/release/create/create_test.go @@ -4,6 +4,7 @@ package create import ( "encoding/json" + "errors" "io" "net/http" "os" @@ -12,6 +13,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + gitlab "gitlab.com/gitlab-org/api/client-go" + gitlabtesting "gitlab.com/gitlab-org/api/client-go/testing" "gitlab.com/gitlab-org/cli/internal/glinstance" "gitlab.com/gitlab-org/cli/internal/testing/cmdtest" @@ -567,6 +572,87 @@ func TestReleaseCreate_MilestoneClosing(t *testing.T) { } } +func TestReleaseCreate_DefaultBranchDetectionForRef(t *testing.T) { + t.Setenv("CI_DEFAULT_BRANCH", "") + + t.Run("use default branch from project API when available", func(t *testing.T) { + tc := gitlabtesting.NewTestClient(t) + + exec := cmdtest.SetupCmdForTest( + t, + NewCmdCreate, + false, + cmdtest.WithGitLabClient(tc.Client), + cmdtest.WithBaseRepo("OWNER", "REPO", glinstance.DefaultHostname), + ) + + notFoundResponse := &gitlab.Response{Response: &http.Response{StatusCode: http.StatusNotFound}} + tc.MockTags.EXPECT().GetTag("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockProjects.EXPECT().GetProject("OWNER/REPO", gomock.Any()).Return(&gitlab.Project{DefaultBranch: "some-default-branch"}, nil, nil) + tc.MockReleases.EXPECT().GetRelease("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockReleases.EXPECT().CreateRelease("OWNER/REPO", &gitlab.CreateReleaseOptions{ + Name: gitlab.Ptr("0.0.1"), + TagName: gitlab.Ptr("0.0.1"), + Ref: gitlab.Ptr("some-default-branch"), + }).Return(&gitlab.Release{}, nil, nil) + + _, err := exec("0.0.1") + require.NoError(t, err) + }) + + t.Run("use default branch from environment if available and project API not available", func(t *testing.T) { + t.Setenv("CI_DEFAULT_BRANCH", "some-default-branch") + + tc := gitlabtesting.NewTestClient(t) + + exec := cmdtest.SetupCmdForTest( + t, + NewCmdCreate, + false, + cmdtest.WithGitLabClient(tc.Client), + cmdtest.WithBaseRepo("OWNER", "REPO", glinstance.DefaultHostname), + ) + + notFoundResponse := &gitlab.Response{Response: &http.Response{StatusCode: http.StatusNotFound}} + tc.MockTags.EXPECT().GetTag("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockProjects.EXPECT().GetProject("OWNER/REPO", gomock.Any()).Return(nil, nil, errors.New("forbidden")) + tc.MockReleases.EXPECT().GetRelease("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockReleases.EXPECT().CreateRelease("OWNER/REPO", &gitlab.CreateReleaseOptions{ + Name: gitlab.Ptr("0.0.1"), + TagName: gitlab.Ptr("0.0.1"), + Ref: gitlab.Ptr("some-default-branch"), + }).Return(&gitlab.Release{}, nil, nil) + + _, err := exec("0.0.1") + require.NoError(t, err) + }) + + t.Run("no explicit ref if default branch not in environment and project API not available", func(t *testing.T) { + tc := gitlabtesting.NewTestClient(t) + + exec := cmdtest.SetupCmdForTest( + t, + NewCmdCreate, + false, + cmdtest.WithGitLabClient(tc.Client), + cmdtest.WithBaseRepo("OWNER", "REPO", glinstance.DefaultHostname), + ) + + notFoundResponse := &gitlab.Response{Response: &http.Response{StatusCode: http.StatusNotFound}} + tc.MockTags.EXPECT().GetTag("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockProjects.EXPECT().GetProject("OWNER/REPO", gomock.Any()).Return(nil, nil, errors.New("forbidden")) + tc.MockReleases.EXPECT().GetRelease("OWNER/REPO", "0.0.1", gomock.Any()).Return(nil, notFoundResponse, errors.New("not found")) + tc.MockReleases.EXPECT().CreateRelease("OWNER/REPO", &gitlab.CreateReleaseOptions{ + Name: gitlab.Ptr("0.0.1"), + TagName: gitlab.Ptr("0.0.1"), + Ref: nil, + }).Return(&gitlab.Release{}, nil, nil) + + _, err := exec("0.0.1") + require.NoError(t, err) + }) +} + func TestReleaseCreate_ExperimentalNotes(t *testing.T) { tests := []struct { name string