feat(github): add option to create PR from forks (#105)
* feat(github): add option to create PR from forks Signed-off-by: mudler <mudler@localai.io> * extend delays in waiting forking from github Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: mudler <mudler@localai.io> Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
087a5fbe0f
commit
5981109730
@@ -3,6 +3,7 @@ package actions
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-github/v69/github"
|
"github.com/google/go-github/v69/github"
|
||||||
"github.com/mudler/LocalAGI/core/types"
|
"github.com/mudler/LocalAGI/core/types"
|
||||||
@@ -10,8 +11,16 @@ import (
|
|||||||
"github.com/sashabaranov/go-openai/jsonschema"
|
"github.com/sashabaranov/go-openai/jsonschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// forkCreationRetries is the number of times to retry checking if a fork is ready
|
||||||
|
forkCreationRetries = 30
|
||||||
|
// forkCreationRetryDelay is the duration to wait between fork creation checks
|
||||||
|
forkCreationRetryDelay = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
type GithubPRCreator struct {
|
type GithubPRCreator struct {
|
||||||
token, repository, owner, customActionName, defaultBranch string
|
token, repository, owner, customActionName, defaultBranch string
|
||||||
|
useFork bool
|
||||||
client *github.Client
|
client *github.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +32,8 @@ func NewGithubPRCreator(config map[string]string) *GithubPRCreator {
|
|||||||
defaultBranch = "main" // Default to "main" if not specified
|
defaultBranch = "main" // Default to "main" if not specified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useFork := config["useFork"] == "true"
|
||||||
|
|
||||||
return &GithubPRCreator{
|
return &GithubPRCreator{
|
||||||
client: client,
|
client: client,
|
||||||
token: config["token"],
|
token: config["token"],
|
||||||
@@ -30,9 +41,45 @@ func NewGithubPRCreator(config map[string]string) *GithubPRCreator {
|
|||||||
owner: config["owner"],
|
owner: config["owner"],
|
||||||
customActionName: config["customActionName"],
|
customActionName: config["customActionName"],
|
||||||
defaultBranch: defaultBranch,
|
defaultBranch: defaultBranch,
|
||||||
|
useFork: useFork,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensureFork ensures that a fork exists for the given repository
|
||||||
|
func (g *GithubPRCreator) ensureFork(ctx context.Context, owner, repo string) (string, error) {
|
||||||
|
// First check if we already have a fork
|
||||||
|
user, _, err := g.client.Users.Get(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get current user: %w", err)
|
||||||
|
}
|
||||||
|
forkOwner := user.GetLogin()
|
||||||
|
|
||||||
|
// Check if fork already exists
|
||||||
|
_, _, err = g.client.Repositories.Get(ctx, forkOwner, repo)
|
||||||
|
if err == nil {
|
||||||
|
// Fork already exists
|
||||||
|
return forkOwner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fork
|
||||||
|
_, _, err = g.client.Repositories.CreateFork(ctx, owner, repo, &github.RepositoryCreateForkOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create fork: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for fork to be ready
|
||||||
|
for i := 0; i < forkCreationRetries; i++ {
|
||||||
|
_, _, err = g.client.Repositories.Get(ctx, forkOwner, repo)
|
||||||
|
if err == nil {
|
||||||
|
return forkOwner, nil
|
||||||
|
}
|
||||||
|
// Sleep for a bit before retrying
|
||||||
|
time.Sleep(forkCreationRetryDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("fork creation timed out")
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName string, owner string, repository string) error {
|
func (g *GithubPRCreator) createOrUpdateBranch(ctx context.Context, branchName string, owner string, repository string) error {
|
||||||
// Get the latest commit SHA from the default branch
|
// Get the latest commit SHA from the default branch
|
||||||
ref, _, err := g.client.Git.GetRef(ctx, owner, repository, "refs/heads/"+g.defaultBranch)
|
ref, _, err := g.client.Git.GetRef(ctx, owner, repository, "refs/heads/"+g.defaultBranch)
|
||||||
@@ -128,15 +175,29 @@ func (g *GithubPRCreator) Run(ctx context.Context, params types.ActionParams) (t
|
|||||||
result.BaseBranch = g.defaultBranch
|
result.BaseBranch = g.defaultBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetOwner, targetRepo string
|
||||||
|
if g.useFork {
|
||||||
|
// Ensure we have a fork and get the fork owner
|
||||||
|
forkOwner, err := g.ensureFork(ctx, result.Owner, result.Repository)
|
||||||
|
if err != nil {
|
||||||
|
return types.ActionResult{}, fmt.Errorf("failed to ensure fork: %w", err)
|
||||||
|
}
|
||||||
|
targetOwner = forkOwner
|
||||||
|
targetRepo = result.Repository
|
||||||
|
} else {
|
||||||
|
targetOwner = result.Owner
|
||||||
|
targetRepo = result.Repository
|
||||||
|
}
|
||||||
|
|
||||||
// Create or update branch
|
// Create or update branch
|
||||||
err = g.createOrUpdateBranch(ctx, result.Branch, result.Owner, result.Repository)
|
err = g.createOrUpdateBranch(ctx, result.Branch, targetOwner, targetRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to create/update branch: %w", err)
|
return types.ActionResult{}, fmt.Errorf("failed to create/update branch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or update files
|
// Create or update files
|
||||||
for _, file := range result.Files {
|
for _, file := range result.Files {
|
||||||
err = g.createOrUpdateFile(ctx, result.Branch, file.Path, file.Content, fmt.Sprintf("Update %s", file.Path), result.Owner, result.Repository)
|
err = g.createOrUpdateFile(ctx, result.Branch, file.Path, file.Content, fmt.Sprintf("Update %s", file.Path), targetOwner, targetRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to update file %s: %w", file.Path, err)
|
return types.ActionResult{}, fmt.Errorf("failed to update file %s: %w", file.Path, err)
|
||||||
}
|
}
|
||||||
@@ -145,7 +206,7 @@ func (g *GithubPRCreator) Run(ctx context.Context, params types.ActionParams) (t
|
|||||||
// Check if PR already exists for this branch
|
// Check if PR already exists for this branch
|
||||||
prs, _, err := g.client.PullRequests.List(ctx, result.Owner, result.Repository, &github.PullRequestListOptions{
|
prs, _, err := g.client.PullRequests.List(ctx, result.Owner, result.Repository, &github.PullRequestListOptions{
|
||||||
State: "open",
|
State: "open",
|
||||||
Head: fmt.Sprintf("%s:%s", result.Owner, result.Branch),
|
Head: fmt.Sprintf("%s:%s", targetOwner, result.Branch),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to list pull requests: %w", err)
|
return types.ActionResult{}, fmt.Errorf("failed to list pull requests: %w", err)
|
||||||
@@ -175,6 +236,12 @@ func (g *GithubPRCreator) Run(ctx context.Context, params types.ActionParams) (t
|
|||||||
Base: &result.BaseBranch,
|
Base: &result.BaseBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If using a fork, we need to specify the full head reference
|
||||||
|
if g.useFork {
|
||||||
|
head := fmt.Sprintf("%s:%s", targetOwner, result.Branch)
|
||||||
|
newPR.Head = &head
|
||||||
|
}
|
||||||
|
|
||||||
createdPR, _, err := g.client.PullRequests.Create(ctx, result.Owner, result.Repository, newPR)
|
createdPR, _, err := g.client.PullRequests.Create(ctx, result.Owner, result.Repository, newPR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.ActionResult{}, fmt.Errorf("failed to create pull request: %w", err)
|
return types.ActionResult{}, fmt.Errorf("failed to create pull request: %w", err)
|
||||||
@@ -322,5 +389,12 @@ func GithubPRCreatorConfigMeta() []config.Field {
|
|||||||
Required: false,
|
Required: false,
|
||||||
HelpText: "Default branch to create PRs against (defaults to main)",
|
HelpText: "Default branch to create PRs against (defaults to main)",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "useFork",
|
||||||
|
Label: "Use Fork",
|
||||||
|
Type: config.FieldTypeCheckbox,
|
||||||
|
Required: false,
|
||||||
|
HelpText: "Whether to create PRs from a fork (useful when you don't have write access to the repository). Set to 'true' to enable.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user