Implementation of a gitea action to create a pull request
This commit is contained in:
		| @@ -1 +1,5 @@ | ||||
| # Gitea action to Create a Pull Request | ||||
|  | ||||
| Gitea action to create a pull request. | ||||
|  | ||||
| Inspired by https://github.com/peter-evans/create-pull-request, but without the ability to commit the changes. | ||||
|   | ||||
							
								
								
									
										25
									
								
								action.yml
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								action.yml
									
									
									
									
									
								
							| @@ -3,3 +3,28 @@ description: 'Create pull requests in gitea' | ||||
| runs: | ||||
|   using: 'go' | ||||
|   main: 'main.go' | ||||
| inputs: | ||||
|   title: | ||||
|     required: false | ||||
|     description: The title of the pull request | ||||
|     default: Changes by create-pull-request action | ||||
|   body: | ||||
|     required: false | ||||
|     description: The body of the pull request | ||||
|     default: Automated changes by create-pull-request Gitea action | ||||
|   labels: | ||||
|     description: 'A comma or newline separated list of labels.' | ||||
|   assignees: | ||||
|     description: 'A comma or newline separated list of assignees (GitHub usernames).' | ||||
|   base: | ||||
|     description: > | ||||
|       The pull request base branch (the one into which the new code will be pushed). | ||||
|     required: true | ||||
|   head: | ||||
|     description: > | ||||
|       The pull request head branch (the one within the new code is developed). | ||||
|   outputs: | ||||
|     pull-request-number: | ||||
|       description: 'The pull request number' | ||||
|     pull-request-url: | ||||
|       description: 'The URL of the pull request.' | ||||
|   | ||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ require ( | ||||
| 	github.com/davidmz/go-pageant v1.0.2 // indirect | ||||
| 	github.com/go-fed/httpsig v1.1.0 // indirect | ||||
| 	github.com/hashicorp/go-version v1.6.0 // indirect | ||||
| 	github.com/sethvargo/go-githubactions v1.2.0 // indirect | ||||
| 	golang.org/x/crypto v0.17.0 // indirect | ||||
| 	golang.org/x/sys v0.15.0 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -8,6 +8,8 @@ github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7 | ||||
| github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= | ||||
| github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/sethvargo/go-githubactions v1.2.0 h1:Gbr36trCAj6uq7Rx1DolY1NTIg0wnzw3/N5WHdKIjME= | ||||
| github.com/sethvargo/go-githubactions v1.2.0/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
|   | ||||
							
								
								
									
										236
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								main.go
									
									
									
									
									
								
							| @@ -2,32 +2,238 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/sdk/gitea" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/sethvargo/go-githubactions" | ||||
| 	"os" | ||||
| 	"slices" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	fmt.Println("hello") | ||||
|  | ||||
| 	client, err := gitea.NewClient("https://gitea.champs-libres.be") | ||||
|  | ||||
| 	if nil != err { | ||||
| 		fmt.Println("could not create a client, {}", err) | ||||
| func ParseActionConfig(ctx githubactions.GitHubContext) (*CreatePrConfig, error) { | ||||
| 	base := githubactions.GetInput("base") | ||||
| 	if base == "" { | ||||
| 		return nil, errors.New("base branch name cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	repos, _, err := client.ListOrgRepos("Chill-project", gitea.ListOrgReposOptions{}) | ||||
| 	repo := strings.Split(ctx.Repository, "/")[1] | ||||
| 	fmt.Printf("The repository is %v\n", repo) | ||||
|  | ||||
| 	if nil != err { | ||||
| 		fmt.Printf("could not list repos, %#v\n", err.Error()) | ||||
| 	title := githubactions.GetInput("title") | ||||
| 	if title == "" { | ||||
| 		return nil, errors.New("title cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range repos { | ||||
| 		fmt.Printf("repository: %v\n", repo.FullName) | ||||
| 	body := githubactions.GetInput("body") | ||||
|  | ||||
| 	assigneesRaw := githubactions.GetInput("assignees") | ||||
| 	assignees := []string{} | ||||
| 	for _, a := range strings.Split(assigneesRaw, ",") { | ||||
| 		assignees = append(assignees, strings.TrimSpace(a)) | ||||
| 	} | ||||
|  | ||||
| 	for _, e := range os.Environ() { | ||||
| 		pair := strings.SplitN(e, "=", 2) | ||||
| 		fmt.Println(pair[0]) | ||||
| 	labelsRaw := githubactions.GetInput("labels") | ||||
| 	labels := []string{} | ||||
| 	for _, l := range strings.Split(labelsRaw, ",") { | ||||
| 		labels = append(labels, strings.TrimSpace(l)) | ||||
| 	} | ||||
|  | ||||
| 	var head string | ||||
| 	headRaw := githubactions.GetInput("head") | ||||
| 	if headRaw == "" { | ||||
| 		if os.Getenv("GITHUB_REF_TYPE") != "branch" { | ||||
| 			return nil, fmt.Errorf("set the \"head\" parameter or work from a branch: only branch can create a pull request: %v given as parameter GITHUB_REF_TYPE", githubactions.GetInput("GITHUB_REF_TYPE")) | ||||
| 		} | ||||
| 		head = os.Getenv("GITHUB_REF_NAME") | ||||
| 	} else { | ||||
| 		head = headRaw | ||||
| 	} | ||||
|  | ||||
| 	return &CreatePrConfig{ | ||||
| 		Org:        ctx.RepositoryOwner, | ||||
| 		Repo:       repo, | ||||
| 		HeadBranch: head, | ||||
| 		BaseBranch: base, | ||||
| 		Title:      title, | ||||
| 		Body:       body, | ||||
| 		Assignees:  assignees, | ||||
| 		Labels:     labels, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	fmt.Println("Starting result CreatePullRequest, main") | ||||
| 	ctx, err := githubactions.Context() | ||||
| 	if err != nil { | ||||
| 		githubactions.Fatalf("could not get context: %v", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	token := os.Getenv("GITHUB_TOKEN") | ||||
| 	fmt.Printf("Api url is %v\n", ctx.ServerURL) | ||||
|  | ||||
| 	config, err := ParseActionConfig(*ctx) | ||||
| 	if err != nil { | ||||
| 		githubactions.Fatalf("%v", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	pr, result, err := createPullRequest(ctx.ServerURL, token, *config) | ||||
| 	if err != nil { | ||||
| 		githubactions.Fatalf("Error while creating pr: %v", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("Created PR with id %d\n", pr.ID) | ||||
|  | ||||
| 	githubactions.SetOutput("pull-request-number", strconv.FormatInt(pr.Index, 10)) | ||||
| 	githubactions.SetOutput("pull-request-result", result) | ||||
| 	githubactions.SetOutput("pull-request-url", pr.URL) | ||||
|  | ||||
| 	fmt.Println("Ending result CreatePullRequest, main") | ||||
| } | ||||
|  | ||||
| // Agent which will perform changes on gitea | ||||
| type Agent struct { | ||||
| 	client *gitea.Client | ||||
| } | ||||
|  | ||||
| // CreatePrConfig represents the configuration for creating a pull request | ||||
| type CreatePrConfig struct { | ||||
| 	// the organization where the PR is created | ||||
| 	Org string | ||||
| 	// the repository where the PR is created | ||||
| 	Repo string | ||||
| 	// the branch where the changes are made | ||||
| 	HeadBranch string | ||||
| 	// the branch where the changes will be added | ||||
| 	BaseBranch string | ||||
| 	// the title of the pull requests | ||||
| 	Title string | ||||
| 	// the body of the pull requests | ||||
| 	Body string | ||||
| 	// the list of assignees | ||||
| 	Assignees []string | ||||
| 	// the list of requested labels | ||||
| 	Labels []string | ||||
| } | ||||
|  | ||||
| // ExistingOpenPullRequest checks if there is an existing pull request for the same head branch and return it | ||||
| func (a *Agent) ExistingOpenPullRequest(config CreatePrConfig) (bool, *gitea.PullRequest, error) { | ||||
| 	currentPage := 1 | ||||
|  | ||||
| 	for currentPage != 0 { | ||||
| 		pulls, response, err := a.client.ListRepoPullRequests(config.Org, config.Repo, | ||||
| 			gitea.ListPullRequestsOptions{State: gitea.StateOpen, ListOptions: gitea.ListOptions{Page: currentPage}}) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return false, nil, err | ||||
| 		} | ||||
|  | ||||
| 		for _, pull := range pulls { | ||||
| 			if pull.Head.Name == config.HeadBranch { | ||||
| 				return true, pull, nil | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		currentPage = response.NextPage | ||||
| 	} | ||||
|  | ||||
| 	return false, nil, nil | ||||
| } | ||||
|  | ||||
| // labelsFromString retrieves a list of labels from a repository based on the provided configuration. | ||||
| // It uses the Gitea client to list all labels in the repository and filters them based on the provided label names. | ||||
| // The method takes a CreatePrConfig struct as a parameter which contains the organization, repository, and label details. | ||||
| // It returns a slice of gitea.Label that matches the provided label names and an error if any occurred. | ||||
| func (a *Agent) labelsFromString(config CreatePrConfig) ([]gitea.Label, error) { | ||||
| 	foundLabels := []gitea.Label{} | ||||
|  | ||||
| 	currentPage := 1 | ||||
| 	for currentPage != 0 { | ||||
| 		labels, response, err := a.client.ListRepoLabels(config.Org, config.Repo, gitea.ListLabelsOptions{ListOptions: gitea.ListOptions{Page: currentPage}}) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		for _, label := range labels { | ||||
| 			if slices.Contains(config.Labels, label.Name) { | ||||
| 				foundLabels = append(foundLabels, *label) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		currentPage = response.NextPage | ||||
| 	} | ||||
|  | ||||
| 	return foundLabels, nil | ||||
| } | ||||
|  | ||||
| // createPullRequestGitea creates a pull request in a Gitea repository based on the provided configuration. | ||||
| // It retrieves the label IDs for the given labels and uses them when creating the pull request. | ||||
| // The method takes a CreatePrConfig struct as a parameter which contains the organization, repository, and pull request details. | ||||
| // It returns the created pull request and any error encountered during the process. | ||||
| func (a *Agent) createPullRequestGitea(config CreatePrConfig) (*gitea.PullRequest, error) { | ||||
| 	labelIds := []int64{} | ||||
| 	labels, err := a.labelsFromString(config) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, label := range labels { | ||||
| 		labelIds = append(labelIds, label.ID) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	pr, _, err := a.client.CreatePullRequest(config.Org, config.Repo, gitea.CreatePullRequestOption{ | ||||
| 		Head:      config.HeadBranch, | ||||
| 		Base:      config.BaseBranch, | ||||
| 		Title:     config.Title, | ||||
| 		Body:      config.Body, | ||||
| 		Assignees: config.Assignees, | ||||
| 		Labels:    labelIds, | ||||
| 		Deadline:  nil, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return pr, nil | ||||
| } | ||||
|  | ||||
| // createPullRequest takes in the API URL, access token, and configuration for creating a pull request. | ||||
| // It initializes a Gitea client using the API URL and access token. | ||||
| // It creates an instance of the Agent struct using the Gitea client. | ||||
| // It checks if there is an open pull request with the given branch name in the repository using the ExistingOpenPullRequest method of Agent. | ||||
| // If an open pull request already exists, it returns nil as the pull request and nil error. | ||||
| // If no open pull request exists, it creates a new pull request using the createPullRequestGitea method of Agent. | ||||
| // It returns the created pull request and nil error if successful. | ||||
| // If there is an error while checking for existing pull requests or creating a new pull request, it returns nil as the pull request and the error. | ||||
| // The function takes in the following parameters: | ||||
| // - apiUrl: The URL of the Gitea API. | ||||
| // - token: The access token for authenticating with the Gitea API. | ||||
| // - config: The configuration for creating the pull request, including the organization, repository, branch details, title, body, assignees, and labels. | ||||
| // It returns a pointer to the created pull request and an error. | ||||
| func createPullRequest(apiUrl string, token string, config CreatePrConfig) (*gitea.PullRequest, string, error) { | ||||
|  | ||||
| 	client, _ := gitea.NewClient(apiUrl, gitea.SetToken(token)) | ||||
| 	agent := &Agent{client: client} | ||||
|  | ||||
| 	has, pull, err := agent.ExistingOpenPullRequest(config) | ||||
| 	if err != nil { | ||||
| 		return nil, "error", err | ||||
| 	} | ||||
|  | ||||
| 	if has { | ||||
| 		return pull, "existing", nil | ||||
| 	} | ||||
|  | ||||
| 	pr, err := agent.createPullRequestGitea(config) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, "error", err | ||||
| 	} | ||||
|  | ||||
| 	return pr, "created", nil | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user