diff --git a/.github/workflows/workflow-executor.yaml b/.github/workflows/workflow-executor.yaml index d7c854ab..3238bb24 100644 --- a/.github/workflows/workflow-executor.yaml +++ b/.github/workflows/workflow-executor.yaml @@ -37,6 +37,10 @@ on: required: false description: "This will set commits to be signed and and verified" type: boolean + granular_commits: + required: false + description: "Granular commits to separate config changes from code changes" + type: boolean speakeasy_server_url: required: false description: "Internal use only" @@ -214,6 +218,7 @@ jobs: mode: ${{ inputs.mode }} force: ${{ inputs.force }} signed_commits: ${{ inputs.signed_commits }} + granular_commits: ${{ inputs.granular_commits }} speakeasy_api_key: ${{ secrets.speakeasy_api_key }} pr_creation_pat: ${{ secrets.pr_creation_pat }} output_tests: ${{ inputs.output_tests }} diff --git a/Makefile b/Makefile index 807e3afd..c5f770f9 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,12 @@ test-direct-mode-multi-sdk: test-pr-mode: docker compose run --rm main ./testing/test.sh ./testing/pr-mode.env +test-pr-mode-granular-commits: + docker compose run --rm main ./testing/test.sh ./testing/pr-mode-granular-commits.env + +test-pr-mode-signed-commits: + docker compose run --rm main ./testing/test.sh ./testing/pr-mode-signed-commits.env + test-push-code-samples-only: docker compose run --rm main ./testing/test.sh ./testing/push-code-samples-only.env diff --git a/action.yml b/action.yml index 91ca4282..deef8f8d 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,10 @@ inputs: description: "Sign commits with GPG" default: "false" required: false + granular_commits: + description: "Commit and push granular changes" + default: "false" + required: false sources: description: "The sources to tag (comma or newline separated)" required: false @@ -202,6 +206,7 @@ runs: - ${{ inputs.branch_name }} - ${{ inputs.cli_output }} - ${{ inputs.signed_commits }} + - ${{ inputs.granular_commits }} - ${{ inputs.previous_gen_version }} - ${{ inputs.openapi_doc_output }} - ${{ inputs.output_tests }} diff --git a/internal/environment/environment.go b/internal/environment/environment.go index a7be5e68..ea5602a6 100644 --- a/internal/environment/environment.go +++ b/internal/environment/environment.go @@ -244,6 +244,10 @@ func GetSignedCommits() bool { return os.Getenv("INPUT_SIGNED_COMMITS") == "true" } +func GetGranularCommits() bool { + return os.Getenv("INPUT_GRANULAR_COMMITS") == "true" +} + func GetBranchName() string { return os.Getenv("INPUT_BRANCH_NAME") } diff --git a/internal/git/git.go b/internal/git/git.go index 7e70007c..e98330d4 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -363,13 +363,19 @@ func (g *Git) CommitAndPush(openAPIDocVersion, speakeasyVersion, doc string, act return "", nil } + logging.Info("Commit and pushing changes to git") + + if environment.GetGranularCommits() && + // TODO: Support signed commits with granular commits + !environment.GetSignedCommits() { + return g.granularCommitAndPush(openAPIDocVersion, speakeasyVersion, doc, action, sourcesOnly) + } + w, err := g.repo.Worktree() if err != nil { return "", fmt.Errorf("error getting worktree: %w", err) } - logging.Info("Commit and pushing changes to git") - if err := g.Add("."); err != nil { return "", fmt.Errorf("error adding changes: %w", err) } @@ -464,6 +470,59 @@ func (g *Git) CommitAndPush(openAPIDocVersion, speakeasyVersion, doc string, act return *commitResult.SHA, nil } +func (g *Git) granularCommitAndPush(openAPIDocVersion, speakeasyVersion, doc string, action environment.Action, sourcesOnly bool) (string, error) { + w, err := g.repo.Worktree() + if err != nil { + return "", fmt.Errorf("error getting worktree: %w", err) + } + + catchAllCommitMessage := "feat: regenerated with Speakeasy CLI" + if action == environment.ActionSuggest { + catchAllCommitMessage = "feat: suggestions for OpenAPI spec" + } + + commits := []struct { + paths []string + msg string + }{ + {paths: []string{"**/.speakeasy/", "*gen.yaml", "*gen.lock", "*workflow.yaml", "*workflow.lock"}, msg: "build: Speakeasy config and lock files"}, + {paths: []string{"*.md"}, msg: "docs: regenerate markdown files"}, + {paths: []string{"."}, msg: catchAllCommitMessage}, + } + + var lastCommitHash plumbing.Hash + for _, commit := range commits { + for _, path := range commit.paths { + if err = g.Add(path); err != nil { + logging.Info(fmt.Errorf("unable to add changes for %v: %w", path, err).Error()) + } + } + + h, err := w.Commit(commit.msg, &git.CommitOptions{ + Author: &object.Signature{ + Name: "speakeasybot", + Email: "bot@speakeasyapi.dev", + When: time.Now(), + }, + AllowEmptyCommits: false, + All: commit.msg == catchAllCommitMessage, + }) + if err != nil { + logging.Info(fmt.Errorf("unable to commit changes for %v: %w", commit.paths, err).Error()) + } else { + lastCommitHash = h + } + } + + if err := g.repo.Push(&git.PushOptions{ + Auth: getGithubAuth(g.accessToken), + Force: true, // This is necessary because at the beginning of the workflow we reset the branch + }); err != nil { + return "", pushErr(err) + } + return lastCommitHash.String(), nil +} + // getOrCreateRef returns the commit branch reference object if it exists or creates it // from the base branch before returning it. func (g *Git) getOrCreateRef(commitRef string) (ref *github.Reference, err error) { @@ -520,6 +579,7 @@ func (g *Git) createAndPushTree(ref *github.Reference, sourceFiles git.Status) ( tree, _, err = g.client.Git.CreateTree(context.Background(), owner, repo, *ref.Object.SHA, entries) return tree, err } + func (g *Git) Add(arg string) error { // We execute this manually because go-git doesn't properly support gitignore cmd := exec.Command("git", "add", arg) diff --git a/testing/pr-mode-granular-commits.env b/testing/pr-mode-granular-commits.env new file mode 100644 index 00000000..8aa9b861 --- /dev/null +++ b/testing/pr-mode-granular-commits.env @@ -0,0 +1,6 @@ +INPUT_MODE="pr" +INPUT_LANGUAGES="- go" +GITHUB_REPOSITORY="speakeasy-api/sdk-generation-action-test-repo" +INPUT_FORCE=true +RUN_FINALIZE=true +INPUT_GRANULAR_COMMITS=true \ No newline at end of file diff --git a/testing/pr-mode-signed-commits.env b/testing/pr-mode-signed-commits.env new file mode 100644 index 00000000..51033be5 --- /dev/null +++ b/testing/pr-mode-signed-commits.env @@ -0,0 +1,6 @@ +INPUT_MODE="pr" +INPUT_LANGUAGES="- go" +GITHUB_REPOSITORY="speakeasy-api/sdk-generation-action-test-repo" +INPUT_FORCE=true +RUN_FINALIZE=true +INPUT_SIGNED_COMMITS=true \ No newline at end of file diff --git a/testing/pr-mode.env b/testing/pr-mode.env index 1310a31e..3d064e71 100644 --- a/testing/pr-mode.env +++ b/testing/pr-mode.env @@ -1,7 +1,6 @@ INPUT_MODE="pr" -INPUT_ACTION="generate" INPUT_LANGUAGES="- go" GITHUB_REPOSITORY="speakeasy-api/sdk-generation-action-test-repo" INPUT_FORCE=true RUN_FINALIZE=true -INPUT_SIGNED_COMMITS=true \ No newline at end of file +INPUT_SIGNED_COMMITS=false \ No newline at end of file diff --git a/testing/test.sh b/testing/test.sh index 21c2ef1e..55e15084 100755 --- a/testing/test.sh +++ b/testing/test.sh @@ -4,7 +4,7 @@ ENV_FILE=$1 function run_action() { rm -rf ./repo || true - rm ./bin/speakeasy || true + rm -f ./bin/speakeasy || true go run main.go } @@ -20,6 +20,6 @@ fi set -o allexport && source ${ENV_FILE} && set +o allexport -rm output.txt || true +rm -f output.txt INPUT_ACTION="run-workflow" run_action