Skip to content

Commit f71e68f

Browse files
committed
feat: Implement setting to skip push events for PR commits
* Add `skip-push-event-for-pr-commits` configuration setting. * Skip processing push events when the commit SHA is present in an open pull request. * Prevent duplicate pipeline runs triggered by both push and pull request events for the same commit. * Document the new configuration setting. Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>
1 parent a2d2175 commit f71e68f

File tree

12 files changed

+1004
-18
lines changed

12 files changed

+1004
-18
lines changed

config/302-pac-configmap.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ data:
143143
# you may want to disable this if ok-to-test should be done on each iteration
144144
remember-ok-to-test: "false"
145145

146+
# When enabled, this option prevents duplicate pipeline runs when a commit appears in
147+
# both a push event and a pull request. If a push event comes from a commit that is
148+
# part of an open pull request, the push event will be skipped as it would create
149+
# a duplicate pipeline run.
150+
# Default: true
151+
skip-push-event-for-pr-commits: "true"
152+
146153
# Configure a custom console here, the driver support custom parameters from
147154
# Repo CR along a few other template variable, see documentation for more
148155
# details

docs/content/docs/install/settings.md

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ There is a few things you can configure through the config map
139139
risk and should be aware of the potential security vulnerabilities.
140140
(only GitHub and Gitea is supported at the moment).
141141

142+
* `skip-push-event-for-pr-commits`
143+
144+
When enabled, this option prevents duplicate pipeline runs when a commit appears in
145+
both a push event and a pull request. If a push event comes from a commit that is
146+
part of an open pull request, the push event will be skipped as it would create
147+
a duplicate pipeline run.
148+
149+
This feature works by checking if a pushed commit SHA exists in any open pull request,
150+
and if so, skipping the push event processing.
151+
152+
Default: `true`
153+
154+
{{< support_matrix github_app="true" github_webhook="true" gitea="true" gitlab="true" bitbucket_cloud="false" bitbucket_datacenter="false" >}}
155+
142156
### Global Cancel In Progress Settings
143157

144158
* `enable-cancel-in-progress-on-pull-requests`
@@ -429,28 +443,28 @@ A few settings are available to configure this feature:
429443
}
430444
```
431445

432-
The `loglevel.*` fields define the log level for the controllers:
446+
The `loglevel.*` fields define the log level for the controllers:
433447

434448
* loglevel.pipelinesascode - the log level for the pipelines-as-code-controller component
435449
* loglevel.pipelines-as-code-webhook - the log level for the pipelines-as-code-webhook component
436450
* loglevel.pac-watcher - the log level for the pipelines-as-code-watcher component
437451

438-
You can change the log level from `info` to `debug` or any other supported values. For example, select the `debug` log level for the pipelines-as-code-watcher component:
452+
You can change the log level from `info` to `debug` or any other supported values. For example, select the `debug` log level for the pipelines-as-code-watcher component:
439453

440-
```bash
441-
kubectl patch configmap pac-config-logging -n pipelines-as-code --type json -p '[{"op": "replace", "path": "/data/loglevel.pac-watcher", "value":"debug"}]'
442-
```
454+
```bash
455+
kubectl patch configmap pac-config-logging -n pipelines-as-code --type json -p '[{"op": "replace", "path": "/data/loglevel.pac-watcher", "value":"debug"}]'
456+
```
443457

444-
After this command, the controller gets a new log level value.
445-
If you want to use the same log level for all Pipelines-as-Code components, delete `level.*` values from configmap:
458+
After this command, the controller gets a new log level value.
459+
If you want to use the same log level for all Pipelines-as-Code components, delete `level.*` values from configmap:
446460

447-
```bash
448-
kubectl patch configmap pac-config-logging -n pipelines-as-code --type json -p '[ {"op": "remove", "path": "/data/loglevel.pac-watcher"}, {"op": "remove", "path": "/data/loglevel.pipelines-as-code-webhook"}, {"op": "remove", "path": "/data/loglevel.pipelinesascode"}]'
449-
```
461+
```bash
462+
kubectl patch configmap pac-config-logging -n pipelines-as-code --type json -p '[ {"op": "remove", "path": "/data/loglevel.pac-watcher"}, {"op": "remove", "path": "/data/loglevel.pipelines-as-code-webhook"}, {"op": "remove", "path": "/data/loglevel.pipelinesascode"}]'
463+
```
450464

451-
In this case, all Pipelines-as-Code components get a common log level from `zap-logger-config` - `level` field from the json.
465+
In this case, all Pipelines-as-Code components get a common log level from `zap-logger-config` - `level` field from the json.
452466

453-
`zap-logger-config` supports the following log levels:
467+
`zap-logger-config` supports the following log levels:
454468

455469
* debug - fine-grained debugging
456470
* info - normal logging
@@ -460,4 +474,4 @@ A few settings are available to configure this feature:
460474
* panic - trigger a panic (crash)
461475
* fatal - immediately exit with exit status 1 (failure)
462476

463-
See more: <https://knative.dev/docs/serving/observability/logging/config-logging>
477+
See more: <https://knative.dev/docs/serving/observability/logging/config-logging>

pkg/params/settings/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ type Settings struct {
6666
EnableCancelInProgressOnPullRequests bool `json:"enable-cancel-in-progress-on-pull-requests"`
6767
EnableCancelInProgressOnPush bool `json:"enable-cancel-in-progress-on-push"`
6868

69+
SkipPushEventForPRCommits bool `json:"skip-push-event-for-pr-commits" default:"true"` // nolint:tagalign
70+
6971
CustomConsoleName string `json:"custom-console-name"`
7072
CustomConsoleURL string `json:"custom-console-url"`
7173
CustomConsolePRdetail string `json:"custom-console-url-pr-details"`

pkg/params/settings/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func TestSyncConfig(t *testing.T) {
4040
ErrorDetectionSimpleRegexp: "^(?P<filename>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+)?([ ]*)?(?P<error>.*)",
4141
EnableCancelInProgressOnPullRequests: false,
4242
EnableCancelInProgressOnPush: false,
43+
SkipPushEventForPRCommits: true,
4344
CustomConsoleName: "",
4445
CustomConsoleURL: "",
4546
CustomConsolePRdetail: "",
@@ -73,6 +74,7 @@ func TestSyncConfig(t *testing.T) {
7374
"custom-console-url-pr-tasklog": "https://custom-console-pr-tasklog",
7475
"custom-console-url-namespace": "https://custom-console-namespace",
7576
"remember-ok-to-test": "false",
77+
"skip-push-event-for-pr-commits": "true",
7678
},
7779
expectedStruct: Settings{
7880
ApplicationName: "pac-pac",
@@ -98,6 +100,7 @@ func TestSyncConfig(t *testing.T) {
98100
CustomConsolePRTaskLog: "https://custom-console-pr-tasklog",
99101
CustomConsoleNamespaceURL: "https://custom-console-namespace",
100102
RememberOKToTest: false,
103+
SkipPushEventForPRCommits: true,
101104
},
102105
},
103106
{

pkg/params/settings/convert_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func TestConvert(t *testing.T) {
3535
"error-log-snippet": "true",
3636
"enable-cancel-in-progress-on-pull-requests": "false",
3737
"enable-cancel-in-progress-on-push": "false",
38+
"skip-push-event-for-pr-commits": "true",
3839
"hub-catalog-name": "tekton",
3940
"hub-url": "https://api.hub.tekton.dev/v1",
4041
"max-keep-run-upper-limit": "0",
@@ -75,6 +76,7 @@ func TestConvert(t *testing.T) {
7576
"error-log-snippet": "true",
7677
"enable-cancel-in-progress-on-pull-requests": "false",
7778
"enable-cancel-in-progress-on-push": "false",
79+
"skip-push-event-for-pr-commits": "true",
7880
"hub-catalog-name": "tekton",
7981
"hub-url": "https://api.hub.tekton.dev/v1",
8082
"max-keep-run-upper-limit": "0",
@@ -116,6 +118,7 @@ func TestConvert(t *testing.T) {
116118
"error-log-snippet": "true",
117119
"enable-cancel-in-progress-on-pull-requests": "false",
118120
"enable-cancel-in-progress-on-push": "false",
121+
"skip-push-event-for-pr-commits": "true",
119122
"hub-catalog-name": "test tekton",
120123
"hub-url": "https://api.hub.tekton.dev/v2",
121124
"max-keep-run-upper-limit": "0",
@@ -169,6 +172,7 @@ func TestConvert(t *testing.T) {
169172
"error-log-snippet": "true",
170173
"enable-cancel-in-progress-on-pull-requests": "false",
171174
"enable-cancel-in-progress-on-push": "false",
175+
"skip-push-event-for-pr-commits": "true",
172176
"hub-catalog-name": "test tekton",
173177
"hub-url": "https://api.hub.tekton.dev/v2",
174178
"max-keep-run-upper-limit": "0",

pkg/provider/gitea/parse_payload.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88

99
giteaStructs "code.gitea.io/gitea/modules/structs"
10+
"code.gitea.io/sdk/gitea"
1011
"github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments"
1112
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
1213
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
@@ -79,6 +80,20 @@ func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http.
7980
processedEvent.BaseURL = gitEvent.Repo.HTMLURL
8081
processedEvent.HeadURL = processedEvent.BaseURL // in push events Head URL is the same as BaseURL
8182
processedEvent.TriggerTarget = "push"
83+
84+
// Only check if the flag is enabled and we have a client
85+
if v.pacInfo != nil && v.pacInfo.SkipPushEventForPRCommits && v.giteaClient != nil {
86+
isPartOfPR, prNumber, err := v.isCommitPartOfPullRequest(processedEvent.Organization, processedEvent.Repository, processedEvent.SHA)
87+
if err != nil {
88+
v.Logger.Warnf("Error checking if push commit is part of PR: %v", err)
89+
}
90+
91+
// If the commit is part of a PR, skip processing the push event
92+
if isPartOfPR {
93+
v.Logger.Infof("Skipping push event for commit %s as it belongs to pull request #%d", processedEvent.SHA, prNumber)
94+
return nil, fmt.Errorf("commit %s is part of pull request #%d, skipping push event", processedEvent.SHA, prNumber)
95+
}
96+
}
8297
case *giteaStructs.IssueCommentPayload:
8398
if gitEvent.Issue.PullRequest == nil {
8499
return info.NewEvent(), fmt.Errorf("issue comment is not coming from a pull_request")
@@ -102,3 +117,43 @@ func (v *Provider) ParsePayload(_ context.Context, _ *params.Run, request *http.
102117
processedEvent.Event = eventInt
103118
return processedEvent, nil
104119
}
120+
121+
// isCommitPartOfPullRequest checks if the commit from a push event is part of an open pull request
122+
// If it is, it returns true and the PR number.
123+
func (v *Provider) isCommitPartOfPullRequest(owner, repo, sha string) (bool, int, error) {
124+
if v.giteaClient == nil {
125+
return false, 0, nil
126+
}
127+
128+
// List all open pull requests in the repository
129+
opts := gitea.ListPullRequestsOptions{
130+
State: gitea.StateOpen,
131+
ListOptions: gitea.ListOptions{
132+
Page: 1,
133+
PageSize: 50,
134+
},
135+
}
136+
137+
prs, _, err := v.Client().ListRepoPullRequests(owner, repo, opts)
138+
if err != nil {
139+
return false, 0, err
140+
}
141+
142+
// Check each PR to see if it contains the commit
143+
for _, pr := range prs {
144+
// Get the commits in this PR
145+
commits, _, err := v.Client().ListPullRequestCommits(owner, repo, pr.Index, gitea.ListPullRequestCommitsOptions{})
146+
if err != nil {
147+
continue
148+
}
149+
150+
// Check if our SHA is in the PR's commits
151+
for _, commit := range commits {
152+
if commit.SHA == sha {
153+
return true, int(pr.Index), nil
154+
}
155+
}
156+
}
157+
158+
return false, 0, nil
159+
}

0 commit comments

Comments
 (0)