Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into github-mergeability-fix
Browse files Browse the repository at this point in the history
jamengual authored Nov 3, 2024
2 parents ebb3e19 + e6e7c91 commit 325dda2
Showing 17 changed files with 294 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ jobs:

steps:
- name: 'Checkout code'
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
show-progress: false
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ ARG GOLANG_TAG=1.23.0-alpine@sha256:d0b31558e6b3e4cc59f6011d79905835108c919143eb
# renovate: datasource=github-releases depName=hashicorp/terraform versioning=hashicorp
ARG DEFAULT_TERRAFORM_VERSION=1.9.8
# renovate: datasource=github-releases depName=opentofu/opentofu versioning=hashicorp
ARG DEFAULT_OPENTOFU_VERSION=1.8.3
ARG DEFAULT_OPENTOFU_VERSION=1.8.4
# renovate: datasource=github-releases depName=open-policy-agent/conftest
ARG DEFAULT_CONFTEST_VERSION=0.56.0

17 changes: 17 additions & 0 deletions runatlantis.io/docs/automerging.md
Original file line number Diff line number Diff line change
@@ -29,6 +29,23 @@ Automerging can be enabled either by:
If automerge is enabled, you can disable it for a single `atlantis apply`
command with the `--auto-merge-disabled` option.

## How to set the merge method for automerge

If automerge is enabled, you can use the `--auto-merge-method` option
for the `atlantis apply` command to specify which merge method use.

```shell
atlantis apply --auto-merge-method <method>
```

The `method` must be one of:

- merge
- rebase
- squash

This is currently only implemented for the GitHub VCS.

## Requirements

### All Plans Must Succeed
1 change: 1 addition & 0 deletions runatlantis.io/docs/using-atlantis.md
Original file line number Diff line number Diff line change
@@ -149,6 +149,7 @@ atlantis apply -w staging
* `-p project` Apply the plan for this project. Refers to the name of the project configured in the repo's [`atlantis.yaml` file](repo-level-atlantis-yaml.md). Cannot be used at same time as `-d` or `-w`.
* `-w workspace` Apply the plan for this [Terraform workspace](https://developer.hashicorp.com/terraform/language/state/workspaces). Ignore this if Terraform workspaces are unused.
* `--auto-merge-disabled` Disable [automerge](automerging.md) for this apply command.
* `--auto-merge-method method` Specify which [merge method](automerging.md#how-to-set-merge-method-for-automerge) use for the apply command if [automerge](automerging.md) is enabled. Implemented only for GitHub.
* `--verbose` Append Atlantis log to comment.

### Additional Terraform flags
1 change: 1 addition & 0 deletions server/core/config/valid/global_cfg.go
Original file line number Diff line number Diff line change
@@ -104,6 +104,7 @@ type MergedProjectCfg struct {
Name string
AutoplanEnabled bool
AutoMergeDisabled bool
AutoMergeMethod string
TerraformVersion *version.Version
RepoCfgVersion int
PolicySets PolicySets
2 changes: 1 addition & 1 deletion server/events/apply_command_runner.go
Original file line number Diff line number Diff line change
@@ -181,7 +181,7 @@ func (a *ApplyCommandRunner) Run(ctx *command.Context, cmd *CommentCommand) {
a.updateCommitStatus(ctx, pullStatus)

if a.autoMerger.automergeEnabled(projectCmds) && !cmd.AutoMergeDisabled {
a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds))
a.autoMerger.automerge(ctx, pullStatus, a.autoMerger.deleteSourceBranchOnMergeEnabled(projectCmds), cmd.AutoMergeMethod)
}
}

3 changes: 2 additions & 1 deletion server/events/automerger.go
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ type AutoMerger struct {
GlobalAutomerge bool
}

func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatus, deleteSourceBranchOnMerge bool) {
func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatus, deleteSourceBranchOnMerge bool, mergeMethod string) {
// We only automerge if all projects have been successfully applied.
for _, p := range pullStatus.Projects {
if p.Status != models.AppliedPlanStatus {
@@ -32,6 +32,7 @@ func (c *AutoMerger) automerge(ctx *command.Context, pullStatus models.PullStatu
ctx.Log.Info("automerging pull request")
var pullOptions models.PullRequestOptions
pullOptions.DeleteSourceBranchOnMerge = deleteSourceBranchOnMerge
pullOptions.MergeMethod = mergeMethod
err := c.VCSClient.MergePull(ctx.Log, ctx.Pull, pullOptions)

if err != nil {
36 changes: 28 additions & 8 deletions server/events/comment_parser.go
Original file line number Diff line number Diff line change
@@ -41,6 +41,8 @@ const (
policySetFlagShort = ""
autoMergeDisabledFlagLong = "auto-merge-disabled"
autoMergeDisabledFlagShort = ""
autoMergeMethodFlagLong = "auto-merge-method"
autoMergeMethodFlagShort = ""
verboseFlagLong = "verbose"
verboseFlagShort = ""
clearPolicyApprovalFlagLong = "clear-policy-approval"
@@ -70,7 +72,7 @@ type CommentBuilder interface {
// BuildPlanComment builds a plan comment for the specified args.
BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string
// BuildApplyComment builds an apply comment for the specified args.
BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string
BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool, autoMergeMethod string) string
// BuildApprovePoliciesComment builds an approve_policies comment for the specified args.
BuildApprovePoliciesComment(repoRelDir string, workspace string, project string) string
}
@@ -226,7 +228,9 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
var project string
var policySet string
var clearPolicyApproval bool
var verbose, autoMergeDisabled bool
var verbose bool
var autoMergeDisabled bool
var autoMergeMethod string
var flagSet *pflag.FlagSet
var name command.Name

@@ -248,6 +252,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Apply the plan for this directory, relative to root of repo, ex. 'child/dir'.")
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", "Apply the plan for this project. Refers to the name of the project configured in a repo config file. Cannot be used at same time as workspace or dir flags.")
flagSet.BoolVarP(&autoMergeDisabled, autoMergeDisabledFlagLong, autoMergeDisabledFlagShort, false, "Disable automerge after apply.")
flagSet.StringVarP(&autoMergeMethod, autoMergeMethodFlagLong, autoMergeMethodFlagShort, "", "Specifies the merge method for the VCS if automerge is enabled. (Currently only implemented for GitHub)")
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
case command.ApprovePolicies.String():
name = command.ApprovePolicies
@@ -317,8 +322,20 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}

if autoMergeMethod != "" {
if autoMergeDisabled {
err := fmt.Sprintf("cannot use --%s at the same time as --%s", autoMergeMethodFlagLong, autoMergeDisabledFlagLong)
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}

if vcsHost != models.Github {
err := fmt.Sprintf("--%s is not currently implemented for %s", autoMergeMethodFlagLong, vcsHost.String())
return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)}
}
}

return CommentParseResult{
Command: NewCommentCommand(dir, extraArgs, name, subName, verbose, autoMergeDisabled, workspace, project, policySet, clearPolicyApproval),
Command: NewCommentCommand(dir, extraArgs, name, subName, verbose, autoMergeDisabled, autoMergeMethod, workspace, project, policySet, clearPolicyApproval),
}
}

@@ -387,7 +404,7 @@ func (e *CommentParser) parseArgs(name command.Name, args []string, flagSet *pfl

// BuildPlanComment builds a plan comment for the specified args.
func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
flags := e.buildFlags(repoRelDir, workspace, project, false)
flags := e.buildFlags(repoRelDir, workspace, project, false, "")
commentFlags := ""
if len(commentArgs) > 0 {
var flagsWithoutQuotes []string
@@ -402,18 +419,18 @@ func (e *CommentParser) BuildPlanComment(repoRelDir string, workspace string, pr
}

// BuildApplyComment builds an apply comment for the specified args.
func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string {
flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled)
func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, project string, autoMergeDisabled bool, autoMergeMethod string) string {
flags := e.buildFlags(repoRelDir, workspace, project, autoMergeDisabled, autoMergeMethod)
return fmt.Sprintf("%s %s%s", e.ExecutableName, command.Apply.String(), flags)
}

// BuildApprovePoliciesComment builds an apply comment for the specified args.
func (e *CommentParser) BuildApprovePoliciesComment(repoRelDir string, workspace string, project string) string {
flags := e.buildFlags(repoRelDir, workspace, project, false)
flags := e.buildFlags(repoRelDir, workspace, project, false, "")
return fmt.Sprintf("%s %s%s", e.ExecutableName, command.ApprovePolicies.String(), flags)
}

func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool) string {
func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string, autoMergeDisabled bool, autoMergeMethod string) string {
// Add quotes if dir has spaces.
if strings.Contains(repoRelDir, " ") {
repoRelDir = fmt.Sprintf("%q", repoRelDir)
@@ -441,6 +458,9 @@ func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project
if autoMergeDisabled {
flags = fmt.Sprintf("%s --%s", flags, autoMergeDisabledFlagLong)
}
if autoMergeMethod != "" {
flags = fmt.Sprintf("%s --%s %s", flags, autoMergeMethodFlagLong, autoMergeMethod)
}
return flags
}

33 changes: 24 additions & 9 deletions server/events/comment_parser_test.go
Original file line number Diff line number Diff line change
@@ -729,6 +729,7 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
workspace string
project string
autoMergeDisabled bool
autoMergeMethod string
commentArgs []string
expPlanFlags string
expApplyFlags string
@@ -824,6 +825,16 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
expApplyFlags: "-d dir -w workspace --auto-merge-disabled",
expVersionFlags: "-d dir -w workspace",
},
{
repoRelDir: "dir",
workspace: "workspace",
project: "",
autoMergeMethod: "squash",
commentArgs: []string{`"arg1"`, `"arg2"`, `arg3`},
expPlanFlags: "-d dir -w workspace -- arg1 arg2 arg3",
expApplyFlags: "-d dir -w workspace --auto-merge-method squash",
expVersionFlags: "-d dir -w workspace",
},
}

for _, c := range cases {
@@ -834,7 +845,7 @@ func TestBuildPlanApplyVersionComment(t *testing.T) {
actComment := commentParser.BuildPlanComment(c.repoRelDir, c.workspace, c.project, c.commentArgs)
Equals(t, fmt.Sprintf("atlantis plan %s", c.expPlanFlags), actComment)
case command.Apply:
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project, c.autoMergeDisabled)
actComment := commentParser.BuildApplyComment(c.repoRelDir, c.workspace, c.project, c.autoMergeDisabled, c.autoMergeMethod)
Equals(t, fmt.Sprintf("atlantis apply %s", c.expApplyFlags), actComment)
}
}
@@ -1020,14 +1031,18 @@ var PlanUsage = `Usage of plan:
`

var ApplyUsage = `Usage of apply:
--auto-merge-disabled Disable automerge after apply.
-d, --dir string Apply the plan for this directory, relative to root of
repo, ex. 'child/dir'.
-p, --project string Apply the plan for this project. Refers to the name of
the project configured in a repo config file. Cannot
be used at same time as workspace or dir flags.
--verbose Append Atlantis log to comment.
-w, --workspace string Apply the plan for this Terraform workspace.
--auto-merge-disabled Disable automerge after apply.
--auto-merge-method string Specifies the merge method for the VCS if
automerge is enabled. (Currently only implemented
for GitHub)
-d, --dir string Apply the plan for this directory, relative to
root of repo, ex. 'child/dir'.
-p, --project string Apply the plan for this project. Refers to the
name of the project configured in a repo config
file. Cannot be used at same time as workspace or
dir flags.
--verbose Append Atlantis log to comment.
-w, --workspace string Apply the plan for this Terraform workspace.
`

var ApprovePolicyUsage = `Usage of approve_policies:
7 changes: 5 additions & 2 deletions server/events/event_parser.go
Original file line number Diff line number Diff line change
@@ -128,6 +128,8 @@ type CommentCommand struct {
SubName string
// AutoMergeDisabled is true if the command should not automerge after apply.
AutoMergeDisabled bool
// AutoMergeMethod specified the merge method for the VCS if automerge enabled.
AutoMergeMethod string
// Verbose is true if the command should output verbosely.
Verbose bool
// Workspace is the name of the Terraform workspace to run the command in.
@@ -177,11 +179,11 @@ func (c CommentCommand) IsAutoplan() bool {

// String returns a string representation of the command.
func (c CommentCommand) String() string {
return fmt.Sprintf("command=%q verbose=%t dir=%q workspace=%q project=%q policyset=%q, clear-policy-approval=%t, flags=%q", c.Name.String(), c.Verbose, c.RepoRelDir, c.Workspace, c.ProjectName, c.PolicySet, c.ClearPolicyApproval, strings.Join(c.Flags, ","))
return fmt.Sprintf("command=%q, verbose=%t, dir=%q, workspace=%q, project=%q, policyset=%q, auto-merge-disabled=%t, auto-merge-method=%s, clear-policy-approval=%t, flags=%q", c.Name.String(), c.Verbose, c.RepoRelDir, c.Workspace, c.ProjectName, c.PolicySet, c.AutoMergeDisabled, c.AutoMergeMethod, c.ClearPolicyApproval, strings.Join(c.Flags, ","))
}

// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults.
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, subName string, verbose, autoMergeDisabled bool, workspace string, project string, policySet string, clearPolicyApproval bool) *CommentCommand {
func NewCommentCommand(repoRelDir string, flags []string, name command.Name, subName string, verbose, autoMergeDisabled bool, autoMergeMethod string, workspace string, project string, policySet string, clearPolicyApproval bool) *CommentCommand {
// If repoRelDir was empty we want to keep it that way to indicate that it
// wasn't specified in the comment.
if repoRelDir != "" {
@@ -198,6 +200,7 @@ func NewCommentCommand(repoRelDir string, flags []string, name command.Name, sub
Verbose: verbose,
Workspace: workspace,
AutoMergeDisabled: autoMergeDisabled,
AutoMergeMethod: autoMergeMethod,
ProjectName: project,
PolicySet: policySet,
ClearPolicyApproval: clearPolicyApproval,
8 changes: 4 additions & 4 deletions server/events/event_parser_test.go
Original file line number Diff line number Diff line change
@@ -750,14 +750,14 @@ func TestNewCommand_CleansDir(t *testing.T) {

for _, c := range cases {
t.Run(c.RepoRelDir, func(t *testing.T) {
cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, "", false, false, "workspace", "", "", false)
cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, "", false, false, "", "workspace", "", "", false)
Equals(t, c.ExpDir, cmd.RepoRelDir)
})
}
}

func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
cmd := events.NewCommentCommand("", nil, command.Plan, "", false, false, "", "", "", false)
cmd := events.NewCommentCommand("", nil, command.Plan, "", false, false, "", "", "", "", false)
Equals(t, events.CommentCommand{
RepoRelDir: "",
Flags: nil,
@@ -769,7 +769,7 @@ func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) {
}

func TestNewCommand_AllFieldsSet(t *testing.T) {
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, "", true, false, "workspace", "project", "policyset", false)
cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, "", true, false, "", "workspace", "project", "policyset", false)
Equals(t, events.CommentCommand{
Workspace: "workspace",
RepoRelDir: "dir",
@@ -816,7 +816,7 @@ func TestCommentCommand_IsAutoplan(t *testing.T) {
}

func TestCommentCommand_String(t *testing.T) {
exp := `command="plan" verbose=true dir="mydir" workspace="myworkspace" project="myproject" policyset="", clear-policy-approval=false, flags="flag1,flag2"`
exp := `command="plan", verbose=true, dir="mydir", workspace="myworkspace", project="myproject", policyset="", auto-merge-disabled=false, auto-merge-method=, clear-policy-approval=false, flags="flag1,flag2"`
Equals(t, exp, (events.CommentCommand{
RepoRelDir: "mydir",
Flags: []string{"flag1", "flag2"},
172 changes: 100 additions & 72 deletions server/events/mocks/mock_comment_building.go
3 changes: 3 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
@@ -185,6 +185,9 @@ type PullRequestOptions struct {
// When DeleteSourceBranchOnMerge flag is set to true VCS deletes the source branch after the PR is merged
// Applied by GitLab & AzureDevops
DeleteSourceBranchOnMerge bool
// MergeMethod specifies the merge method for the VCS
// Implemented only for Github
MergeMethod string
}

type PullRequestState int
4 changes: 2 additions & 2 deletions server/events/project_command_context_builder.go
Original file line number Diff line number Diff line change
@@ -130,7 +130,7 @@ func (cb *DefaultProjectCommandContextBuilder) BuildProjectContext(
projectCmdContext := newProjectCommandContext(
ctx,
cmdName,
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled),
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled, prjCfg.AutoMergeMethod),
cb.CommentBuilder.BuildApprovePoliciesComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
prjCfg,
@@ -203,7 +203,7 @@ func (cb *PolicyCheckProjectCommandContextBuilder) BuildProjectContext(
projectCmds = append(projectCmds, newProjectCommandContext(
ctx,
command.PolicyCheck,
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled),
cb.CommentBuilder.BuildApplyComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, prjCfg.AutoMergeDisabled, prjCfg.AutoMergeMethod),
cb.CommentBuilder.BuildApprovePoliciesComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name),
cb.CommentBuilder.BuildPlanComment(prjCfg.RepoRelDir, prjCfg.Workspace, prjCfg.Name, commentFlags),
prjCfg,
8 changes: 4 additions & 4 deletions server/events/project_command_context_builder_test.go
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {

t.Run("with project name defined", func(t *testing.T) {
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, projName, []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, projName, false)).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, projName, false, "")).ThenReturn(expectedApplyCmt)

pullStatus.Projects = []models.ProjectStatus{
{
@@ -68,7 +68,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
t.Run("with no project name defined", func(t *testing.T) {
projCfg.Name = ""
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, "", []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false)).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false, "")).ThenReturn(expectedApplyCmt)
pullStatus.Projects = []models.ProjectStatus{
{
Status: models.ErroredPlanStatus,
@@ -88,7 +88,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
t.Run("when ParallelApply is set to true", func(t *testing.T) {
projCfg.Name = "Apply Comment"
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, "", []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false)).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false, "")).ThenReturn(expectedApplyCmt)
pullStatus.Projects = []models.ProjectStatus{
{
Status: models.ErroredPlanStatus,
@@ -109,7 +109,7 @@ func TestProjectCommandContextBuilder_PullStatus(t *testing.T) {
t.Run("when AbortOnExcecutionOrderFail is set to true", func(t *testing.T) {
projCfg.Name = "Apply Comment"
When(mockCommentBuilder.BuildPlanComment(projRepoRelDir, projWorkspace, "", []string{})).ThenReturn(expectedPlanCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false)).ThenReturn(expectedApplyCmt)
When(mockCommentBuilder.BuildApplyComment(projRepoRelDir, projWorkspace, "", false, "")).ThenReturn(expectedApplyCmt)
pullStatus.Projects = []models.ProjectStatus{
{
Status: models.ErroredPlanStatus,
42 changes: 35 additions & 7 deletions server/events/vcs/github_client.go
Original file line number Diff line number Diff line change
@@ -17,7 +17,10 @@ import (
"context"
"encoding/base64"
"fmt"
"maps"
"net/http"
"slices"
"sort"
"strconv"
"strings"
"time"
@@ -891,7 +894,7 @@ func (g *GithubClient) UpdateStatus(logger logging.SimpleLogging, repo models.Re
}

// MergePull merges the pull request.
func (g *GithubClient) MergePull(logger logging.SimpleLogging, pull models.PullRequest, _ models.PullRequestOptions) error {
func (g *GithubClient) MergePull(logger logging.SimpleLogging, pull models.PullRequest, pullOptions models.PullRequestOptions) error {
logger.Debug("Merging GitHub pull request %d", pull.Num)
// Users can set their repo to disallow certain types of merging.
// We detect which types aren't allowed and use the type that is.
@@ -902,17 +905,42 @@ func (g *GithubClient) MergePull(logger logging.SimpleLogging, pull models.PullR
if err != nil {
return errors.Wrap(err, "fetching repo info")
}

const (
defaultMergeMethod = "merge"
rebaseMergeMethod = "rebase"
squashMergeMethod = "squash"
)
method := defaultMergeMethod
if !repo.GetAllowMergeCommit() {
if repo.GetAllowRebaseMerge() {
method = rebaseMergeMethod
} else if repo.GetAllowSquashMerge() {
method = squashMergeMethod

mergeMethodsAllow := map[string]func() bool{
defaultMergeMethod: repo.GetAllowMergeCommit,
rebaseMergeMethod: repo.GetAllowRebaseMerge,
squashMergeMethod: repo.GetAllowSquashMerge,
}

mergeMethodsName := slices.Collect(maps.Keys(mergeMethodsAllow))
sort.Strings(mergeMethodsName)

var method string
if pullOptions.MergeMethod != "" {
method = pullOptions.MergeMethod

isMethodAllowed, isMethodExist := mergeMethodsAllow[method]
if !isMethodExist {
return fmt.Errorf("Merge method '%s' is unknown. Specify one of the valid values: '%s'", method, strings.Join(mergeMethodsName, ", "))
}

if !isMethodAllowed() {
return fmt.Errorf("Merge method '%s' is not allowed by the repository Pull Request settings", method)
}
} else {
method = defaultMergeMethod
if !repo.GetAllowMergeCommit() {
if repo.GetAllowRebaseMerge() {
method = rebaseMergeMethod
} else if repo.GetAllowSquashMerge() {
method = squashMergeMethod
}
}
}

70 changes: 65 additions & 5 deletions server/events/vcs/github_client_test.go
Original file line number Diff line number Diff line change
@@ -995,10 +995,12 @@ func TestGithubClient_MergePullHandlesError(t *testing.T) {
func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
logger := logging.NewNoopLogger(t)
cases := map[string]struct {
allowMerge bool
allowRebase bool
allowSquash bool
expMethod string
allowMerge bool
allowRebase bool
allowSquash bool
mergeMethodOption string
expMethod string
expErr string
}{
"all true": {
allowMerge: true,
@@ -1030,6 +1032,59 @@ func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
allowSquash: false,
expMethod: "rebase",
},
"all true: merge with merge: overrided by command": {
allowMerge: true,
allowRebase: true,
allowSquash: true,
mergeMethodOption: "merge",
expMethod: "merge",
},
"all true: merge with rebase: overrided by command": {
allowMerge: true,
allowRebase: true,
allowSquash: true,
mergeMethodOption: "rebase",
expMethod: "rebase",
},
"all true: merge with squash: overrided by command": {
allowMerge: true,
allowRebase: true,
allowSquash: true,
mergeMethodOption: "squash",
expMethod: "squash",
},
"merge with merge: overridden by command: merge not allowed": {
allowMerge: false,
allowRebase: true,
allowSquash: true,
mergeMethodOption: "merge",
expMethod: "",
expErr: "Merge method 'merge' is not allowed by the repository Pull Request settings",
},
"merge with rebase: overridden by command: rebase not allowed": {
allowMerge: true,
allowRebase: false,
allowSquash: true,
mergeMethodOption: "rebase",
expMethod: "",
expErr: "Merge method 'rebase' is not allowed by the repository Pull Request settings",
},
"merge with squash: overridden by command: squash not allowed": {
allowMerge: true,
allowRebase: true,
allowSquash: false,
mergeMethodOption: "squash",
expMethod: "",
expErr: "Merge method 'squash' is not allowed by the repository Pull Request settings",
},
"merge with unknown: overridden by command: unknown doesn't exist": {
allowMerge: true,
allowRebase: true,
allowSquash: true,
mergeMethodOption: "unknown",
expMethod: "",
expErr: "Merge method 'unknown' is unknown. Specify one of the valid values: 'merge, rebase, squash'",
},
}

for name, c := range cases {
@@ -1104,9 +1159,14 @@ func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
Num: 1,
}, models.PullRequestOptions{
DeleteSourceBranchOnMerge: false,
MergeMethod: c.mergeMethodOption,
})

Ok(t, err)
if c.expErr == "" {
Ok(t, err)
} else {
ErrContains(t, c.expErr, err)
}
})
}
}

0 comments on commit 325dda2

Please sign in to comment.