Skip to content

Commit 6b1a69a

Browse files
authored
Add conditional merge configuration (#270)
* 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
1 parent d4e4302 commit 6b1a69a

File tree

6 files changed

+544
-51
lines changed

6 files changed

+544
-51
lines changed

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,36 @@ merge:
126126
# "rebase", "squash", and "ff-only".
127127
method: squash
128128

129-
# Allows the merge method that is used when auto-merging a PR to be different based on the
129+
##### branch_method has been DEPRECATED in favor of merge_method #####
130+
#
131+
# Allows the merge method that is used when auto-merging a PR to be different
130132
# target branch. The keys of the hash are the target branch name, and the values are the merge method that
131133
# will be used for PRs targeting that branch. The valid values are the same as for the "method" key.
132134
# Note: If the target branch does not match any of the specified keys, the "method" key is used instead.
133135
branch_method:
134136
develop: squash
135137
master: merge
138+
##### branch_method has been DEPRECATED in favor of merge_method #####
139+
140+
# Allows the merge method that is used when auto-merging a PR to be different
141+
# based on trigger criteria. The first method where ALL triggers match will
142+
# be used. Otherwise, the method specified previously in "merge.method" will
143+
# be used.
144+
# - ALL trigger criteria must match, unlike merge/trigger where ANY match
145+
# will trigger bulldozer.
146+
# - This will override any branch_method logic if one of the methods is
147+
# triggered
148+
# - If no trigger criteria is provided the method is ignored
149+
merge_method:
150+
# "method" defines the merge method. The available options are "merge",
151+
# "rebase", "squash", and "ff-only".
152+
- method: squash
153+
trigger:
154+
# All methods from merge/trigger are supported. Additionally, the
155+
# following additional methods are provided:
156+
157+
# Pull requests which a number of commits less than or equal to this value are added to the trigger.
158+
max_commits: 3
136159

137160
# "options" defines additional options for the individual merge methods.
138161
options:

bulldozer/config_v1.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ type MergeConfig struct {
4444
DeleteAfterMerge bool `yaml:"delete_after_merge"`
4545
AllowMergeWithNoChecks bool `yaml:"allow_merge_with_no_checks"`
4646

47-
Method MergeMethod `yaml:"method"`
48-
Options MergeOptions `yaml:"options"`
47+
Method MergeMethod `yaml:"method"`
48+
MergeMethods []ConditionalMergeMethod `yaml:"merge_method"`
49+
Options MergeOptions `yaml:"options"`
4950

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

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

62+
type ConditionalMergeMethod struct {
63+
Method MergeMethod `yaml:"method"`
64+
Trigger Signals `yaml:"trigger"`
65+
}
66+
6167
type SquashOptions struct {
6268
Title TitleStrategy `yaml:"title"`
6369
Body MessageStrategy `yaml:"body"`

bulldozer/evaluate.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,29 @@ import (
2626
// IsPRIgnored returns true if the PR is identified as ignored,
2727
// false otherwise. Additionally, a description of the reason will be returned.
2828
func IsPRIgnored(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
29-
matches, reason, err := config.Matches(ctx, pullCtx, "ignored")
29+
matches, reason, err := config.MatchesAny(ctx, pullCtx, "ignored")
3030
if err != nil {
3131
// ignore must always fail closed (matches on error)
32-
matches = true
32+
return true, reason, err
3333
}
3434
return matches, reason, err
3535
}
3636

3737
// IsPRTriggered returns true if the PR is identified as triggered,
3838
// false otherwise. Additionally, a description of the reason will be returned.
3939
func IsPRTriggered(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
40-
matches, reason, err := config.Matches(ctx, pullCtx, "triggered")
40+
matches, reason, err := config.MatchesAny(ctx, pullCtx, "triggered")
41+
if err != nil {
42+
// trigger must always fail closed (no match on error)
43+
return false, reason, err
44+
}
45+
return matches, reason, err
46+
}
47+
48+
// IsMergeMethodTriggered returns true if ALL signals are fully matched,
49+
// false otherwise. Additionally, a description of the reason will be returned.
50+
func IsMergeMethodTriggered(ctx context.Context, pullCtx pull.Context, config Signals) (bool, string, error) {
51+
matches, reason, err := config.MatchesAll(ctx, pullCtx, "triggered")
4152
if err != nil {
4253
// trigger must always fail closed (no match on error)
4354
return false, reason, err

bulldozer/merge.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,49 @@ func (m *PushRestrictionMerger) DeleteHead(ctx context.Context, pullCtx pull.Con
149149
return m.Normal.DeleteHead(ctx, pullCtx)
150150
}
151151

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

157-
base, head := pullCtx.Branches()
156+
base, _ := pullCtx.Branches()
158157
mergeMethod := mergeConfig.Method
159158

160159
if branchMergeMethod, ok := mergeConfig.BranchMethod[base]; ok {
161160
mergeMethod = branchMergeMethod
162161
}
162+
163+
for _, method := range mergeConfig.MergeMethods {
164+
triggered, reason, err := IsMergeMethodTriggered(ctx, pullCtx, method.Trigger)
165+
if err != nil {
166+
err = errors.Wrapf(err, "Failed to determine if merge method '%s' is triggered", method.Method)
167+
return "", err
168+
}
169+
170+
if triggered {
171+
mergeMethod = method.Method
172+
logger.Debug().Msgf("%s method is triggered because %s matched", mergeMethod, reason)
173+
break
174+
}
175+
}
176+
163177
if !isValidMergeMethod(mergeMethod) {
164178
mergeMethod = MergeCommit
165179
}
166180

181+
return mergeMethod, nil
182+
}
183+
184+
// MergePR merges a pull request if all conditions are met. It logs any errors
185+
// that it encounters.
186+
func MergePR(ctx context.Context, pullCtx pull.Context, merger Merger, mergeConfig MergeConfig) {
187+
logger := zerolog.Ctx(ctx)
188+
189+
mergeMethod, err := DetermineMergeMethod(ctx, pullCtx, mergeConfig)
190+
if err != nil {
191+
logger.Error().Err(err).Msg("Failed to determine merge method")
192+
return
193+
}
194+
167195
commitMsg := CommitMessage{}
168196
if mergeMethod == SquashAndMerge {
169197
opt := mergeConfig.Options.Squash
@@ -210,6 +238,7 @@ func MergePR(ctx context.Context, pullCtx pull.Context, merger Merger, mergeConf
210238
time.Sleep(4 * time.Second)
211239
}
212240

241+
_, head := pullCtx.Branches()
213242
if merged {
214243
if mergeConfig.DeleteAfterMerge {
215244
attemptDelete(ctx, pullCtx, head, merger)

0 commit comments

Comments
 (0)