Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 15 additions & 31 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,26 +315,6 @@ func (g *Git) resetWorktree(worktree *git.Worktree, branchName string) error {
return nil
}

func (g *Git) cherryPick(commitHash string) error {
logging.Info("Cherry-picking commit %s", commitHash)

workDir := filepath.Join(environment.GetWorkspace(), "repo", environment.GetWorkingDirectory())

// Use -c flags to set identity temporarily for just this command
cmd := exec.Command("git",
"-c", "user.name="+speakeasyBotName,
"-c", "[email protected]",
"cherry-pick", "--allow-empty", commitHash)
cmd.Dir = workDir
cmd.Env = os.Environ()
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error cherry-picking commit %s: %w %s", commitHash, err, string(output))
}

return nil
}

func (g *Git) FindOrCreateBranch(branchName string, action environment.Action) (string, error) {
if g.repo == nil {
return "", fmt.Errorf("repo not cloned")
Expand Down Expand Up @@ -366,24 +346,28 @@ func (g *Git) FindOrCreateBranch(branchName string, action environment.Action) (
return "", err
}

// If there are non-CI commits, fail immediately with an error
if len(nonCICommits) > 0 {
logging.Info("Found %d non-CI commits on branch %s", len(nonCICommits), branchName)

// Try to find the associated PR to provide a direct link
_, pr, prErr := g.FindExistingPR(branchName, action, false)
if prErr == nil && pr != nil {
prURL := pr.GetHTMLURL()
return "", fmt.Errorf("external changes detected on branch %s. The action cannot proceed because non-automated commits were pushed to this branch.\n\nPlease either:\n- Merge the PR: %s\n- Close the PR and delete the branch\n\nAfter merging or closing, the action will create a new branch on the next run", branchName, prURL)
}

// Fallback error if PR not found
return "", fmt.Errorf("external changes detected on branch %s. The action cannot proceed because non-automated commits were pushed to this branch.\n\nPlease either:\n- Merge the associated PR for this branch\n- Close the PR and delete the branch\n\nAfter merging or closing, the action will create a new branch on the next run", branchName)
}

// Reset to clean baseline from main
origin := fmt.Sprintf("origin/%s", defaultBranch)
if err = g.Reset("--hard", origin); err != nil {
// Swallow this error for now. Functionality will be unchanged from previous behavior if it fails
logging.Info("failed to reset branch: %s", err.Error())
}

// We will attempt to cherry-pick non Speakeasy generated commits onto the fresh branch
if len(nonCICommits) > 0 {
logging.Info("Cherry-picking %d non-CI commits onto fresh branch", len(nonCICommits))
// Reverse the order since git log returns newest first, but we want to apply oldest first
for i := len(nonCICommits) - 1; i >= 0; i-- {
if err := g.cherryPick(nonCICommits[i]); err != nil {
return "", fmt.Errorf("failed to cherry-pick commit %s: %w\n\nThis likely means manual changes are modifying a generated portion of the SDK.", nonCICommits[i][:8], err)
}
}
}

return existingBranch, nil
}

Expand Down
21 changes: 6 additions & 15 deletions internal/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,26 +456,17 @@ func TestGit_FindOrCreateBranch_NonCIPendingCommits(t *testing.T) {
repo, err := git.PlainOpen(repoPath)
require.NoError(t, err)

g := Git{repo: repo}
g := New("test-token")
g.repo = repo
runGitCLI(t, repoPath, "config", "pull.rebase", "false")

t.Setenv("GITHUB_WORKSPACE", workspace)
t.Setenv("INPUT_WORKING_DIRECTORY", "")

branchName, err := g.FindOrCreateBranch("regen", environment.ActionRunWorkflow)
require.NoError(t, err)
assert.Equal(t, "regen", branchName)

// Verify the manual commit was cherry-picked onto the fresh branch
runGitCLI(t, repoPath, "checkout", "regen")
content, err := os.ReadFile(filepath.Join(repoPath, "manual.txt"))
require.NoError(t, err)
assert.Equal(t, "manual change\n", string(content), "manual commit should be preserved via cherry-pick")

// Verify CI commit was discarded (generated.txt should be back to main's version)
generatedContent, err := os.ReadFile(filepath.Join(repoPath, "generated.txt"))
require.NoError(t, err)
assert.Equal(t, "original\n", string(generatedContent), "CI commit should be discarded, file should match main")
_, err = g.FindOrCreateBranch("regen", environment.ActionRunWorkflow)
require.Error(t, err)
expectedError := "external changes detected on branch regen. The action cannot proceed because non-automated commits were pushed to this branch.\n\nPlease either:\n- Merge the associated PR for this branch\n- Close the PR and delete the branch\n\nAfter merging or closing, the action will create a new branch on the next run"
assert.Equal(t, expectedError, err.Error())
}

func TestGit_FindOrCreateBranch_BotCommitAllowed(t *testing.T) {
Expand Down
Loading