Skip to content

Commit

Permalink
Add conditional merge configuration (#270)
Browse files Browse the repository at this point in the history
* Rename Matches to MatchesAny

* Remove comment

* Add Signal type

* Refactor Enabled method to use size

* Refactor MatchesAny into several smaller functions

* Add errors package

* Add MatchesAll

* Add conditional merge config

* Add IsMergeMethodTriggered

* Allow merge method to be overridden

* Fix typo

* Add better quick evaluation

* Clarify comments

* Remove TODOs

* Add maxcommits logic

* Ensure matches are updated

* Validate body content and comments

* Remove redundant checks

* Ensure MaxCommits is counted correctly

* Add tests for MaxCommits

* Add merge_method docs

* Allow merge_method to override branch_method

* Fix typo

* Use for loop

* Move merge logic to DetermineMergeMethod

* Add note about overriding branch_method

* Remove SignalsMatches

* Return a MergeMethod

* Fix error where head is not defined

* Refactor size method to return the count of non-zero value signals

* Remove typo

* Rename MergeMethods

* Format golang

* Update signatures in MatchesAny

* Updates signatures in MatchesAll

* Update all matches method signatures

* Move all matching methods to use new logic

* Use separate types for each signal

* Add additional tests

* Add Matches

* Consistently use signal as the receiver name

* Remove else clauses

* Return directly in IsPRIgnored

* Move logging

* Move comments

* Change signal methods to no longer use pointers

* Wrap call to Match with a check that it is enabled

* Remove logging

* Add back indentation

* Remove unused loggers

* Fix readme comment

* Fix logic

* Return False for MatchesAll with no signals

* Return false early for MatchesAny without any signals
  • Loading branch information
derekjobst authored Jan 25, 2022
1 parent d4e4302 commit 6b1a69a
Show file tree
Hide file tree
Showing 6 changed files with 544 additions and 51 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,36 @@ merge:
# "rebase", "squash", and "ff-only".
method: squash

# Allows the merge method that is used when auto-merging a PR to be different based on the
##### branch_method has been DEPRECATED in favor of merge_method #####
#
# Allows the merge method that is used when auto-merging a PR to be different
# target branch. The keys of the hash are the target branch name, and the values are the merge method that
# will be used for PRs targeting that branch. The valid values are the same as for the "method" key.
# Note: If the target branch does not match any of the specified keys, the "method" key is used instead.
branch_method:
develop: squash
master: merge
##### branch_method has been DEPRECATED in favor of merge_method #####

# Allows the merge method that is used when auto-merging a PR to be different
# based on trigger criteria. The first method where ALL triggers match will
# be used. Otherwise, the method specified previously in "merge.method" will
# be used.
# - ALL trigger criteria must match, unlike merge/trigger where ANY match
# will trigger bulldozer.
# - This will override any branch_method logic if one of the methods is
# triggered
# - If no trigger criteria is provided the method is ignored
merge_method:
# "method" defines the merge method. The available options are "merge",
# "rebase", "squash", and "ff-only".
- method: squash
trigger:
# All methods from merge/trigger are supported. Additionally, the
# following additional methods are provided:

# Pull requests which a number of commits less than or equal to this value are added to the trigger.
max_commits: 3

# "options" defines additional options for the individual merge methods.
options:
Expand Down
10 changes: 8 additions & 2 deletions bulldozer/config_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ type MergeConfig struct {
DeleteAfterMerge bool `yaml:"delete_after_merge"`
AllowMergeWithNoChecks bool `yaml:"allow_merge_with_no_checks"`

Method MergeMethod `yaml:"method"`
Options MergeOptions `yaml:"options"`
Method MergeMethod `yaml:"method"`
MergeMethods []ConditionalMergeMethod `yaml:"merge_method"`
Options MergeOptions `yaml:"options"`

BranchMethod map[string]MergeMethod `yaml:"branch_method"`

Expand All @@ -58,6 +59,11 @@ type MergeOptions struct {
Squash *SquashOptions `yaml:"squash"`
}

type ConditionalMergeMethod struct {
Method MergeMethod `yaml:"method"`
Trigger Signals `yaml:"trigger"`
}

type SquashOptions struct {
Title TitleStrategy `yaml:"title"`
Body MessageStrategy `yaml:"body"`
Expand Down
17 changes: 14 additions & 3 deletions bulldozer/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,29 @@ import (
// IsPRIgnored returns true if the PR is identified as ignored,
// false otherwise. Additionally, a description of the reason will be returned.
func IsPRIgnored(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
matches, reason, err := config.Matches(ctx, pullCtx, "ignored")
matches, reason, err := config.MatchesAny(ctx, pullCtx, "ignored")
if err != nil {
// ignore must always fail closed (matches on error)
matches = true
return true, reason, err
}
return matches, reason, err
}

// IsPRTriggered returns true if the PR is identified as triggered,
// false otherwise. Additionally, a description of the reason will be returned.
func IsPRTriggered(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
matches, reason, err := config.Matches(ctx, pullCtx, "triggered")
matches, reason, err := config.MatchesAny(ctx, pullCtx, "triggered")
if err != nil {
// trigger must always fail closed (no match on error)
return false, reason, err
}
return matches, reason, err
}

// IsMergeMethodTriggered returns true if ALL signals are fully matched,
// false otherwise. Additionally, a description of the reason will be returned.
func IsMergeMethodTriggered(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
matches, reason, err := config.MatchesAll(ctx, pullCtx, "triggered")
if err != nil {
// trigger must always fail closed (no match on error)
return false, reason, err
Expand Down
37 changes: 33 additions & 4 deletions bulldozer/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,21 +149,49 @@ func (m *PushRestrictionMerger) DeleteHead(ctx context.Context, pullCtx pull.Con
return m.Normal.DeleteHead(ctx, pullCtx)
}

// MergePR merges a pull request if all conditions are met. It logs any errors
// that it encounters.
func MergePR(ctx context.Context, pullCtx pull.Context, merger Merger, mergeConfig MergeConfig) {
// DetermineMergeMethod determines which merge method to use when merging the PR
func DetermineMergeMethod(ctx context.Context, pullCtx pull.Context, mergeConfig MergeConfig) (MergeMethod, error) {
logger := zerolog.Ctx(ctx)

base, head := pullCtx.Branches()
base, _ := pullCtx.Branches()
mergeMethod := mergeConfig.Method

if branchMergeMethod, ok := mergeConfig.BranchMethod[base]; ok {
mergeMethod = branchMergeMethod
}

for _, method := range mergeConfig.MergeMethods {
triggered, reason, err := IsMergeMethodTriggered(ctx, pullCtx, method.Trigger)
if err != nil {
err = errors.Wrapf(err, "Failed to determine if merge method '%s' is triggered", method.Method)
return "", err
}

if triggered {
mergeMethod = method.Method
logger.Debug().Msgf("%s method is triggered because %s matched", mergeMethod, reason)
break
}
}

if !isValidMergeMethod(mergeMethod) {
mergeMethod = MergeCommit
}

return mergeMethod, nil
}

// MergePR merges a pull request if all conditions are met. It logs any errors
// that it encounters.
func MergePR(ctx context.Context, pullCtx pull.Context, merger Merger, mergeConfig MergeConfig) {
logger := zerolog.Ctx(ctx)

mergeMethod, err := DetermineMergeMethod(ctx, pullCtx, mergeConfig)
if err != nil {
logger.Error().Err(err).Msg("Failed to determine merge method")
return
}

commitMsg := CommitMessage{}
if mergeMethod == SquashAndMerge {
opt := mergeConfig.Options.Squash
Expand Down Expand Up @@ -210,6 +238,7 @@ func MergePR(ctx context.Context, pullCtx pull.Context, merger Merger, mergeConf
time.Sleep(4 * time.Second)
}

_, head := pullCtx.Branches()
if merged {
if mergeConfig.DeleteAfterMerge {
attemptDelete(ctx, pullCtx, head, merger)
Expand Down
Loading

0 comments on commit 6b1a69a

Please sign in to comment.