Skip to content

Commit 30a2276

Browse files
committed
Add positive and negative head branch filters
For some workflows, it can be useful to treat pull requests differently depending on the name of the head branch for the pull request. For example, a team following scaled trunk-based development might use the `release/*` naming convention for release branches, and may have different build/deploy processes for those release branches. This adds `head_branches` and `ignore_head_branches` settings to this resource, specified as a glob pattern to align with similar filters on the Concourse git resource, so that teams can run different jobs based on the head branch of a PR. See: https://trunkbaseddevelopment.com/#scaled-trunk-based-development
1 parent 9ec47e2 commit 30a2276

5 files changed

Lines changed: 118 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Make sure to check out [#migrating](#migrating) to learn more.
3434
| `required_review_approvals` | No | `2` | Disable triggering of the resource if the pull request does not have at least `X` approved review(s). |
3535
| `git_crypt_key` | No | `AEdJVENSWVBUS0VZAAAAA...` | Base64 encoded git-crypt key. Setting this will unlock / decrypt the repository with git-crypt. To get the key simply execute `git-crypt export-key -- - | base64` in an encrypted repository. |
3636
| `base_branch` | No | `master` | Name of a branch. The pipeline will only trigger on pull requests against the specified branch. |
37+
| `head_branches` | No | `release/*` | If specified, the pipeline will only trigger on pull requests for which the head branch name matches the specified glob pattern. |
38+
| `ignore_head_branches` | No | `feature/*` | Inverse of the above |
3739
| `labels` | No | `["bug", "enhancement"]` | The labels on the PR. The pipeline will only trigger on pull requests having at least one of the specified labels. |
3840
| `disable_git_lfs` | No | `true` | Disable Git LFS, skipping an attempt to convert pointers of files tracked into their corresponding objects when checked out into a working copy. |
3941
| `states` | No | `["OPEN", "MERGED"]` | The PR states to select (`OPEN`, `MERGED` or `CLOSED`). The pipeline will only trigger on pull requests matching one of the specified states. Default is ["OPEN"]. |

check.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ Loop:
4949
continue
5050
}
5151

52+
// Skip pull request if the HeadRefName does not match the head_branches glob specified in source
53+
if request.Source.HeadBranches != "" {
54+
matched, err := filepath.Match(request.Source.HeadBranches, p.PullRequestObject.HeadRefName)
55+
if err != nil {
56+
return nil, fmt.Errorf("failed to apply head branch filter: %s", err)
57+
}
58+
if !matched {
59+
continue
60+
}
61+
}
62+
63+
// Skip pull request if the HeadRefName matches the ignore_head_branches glob specified in source
64+
if request.Source.IgnoreHeadBranches != "" {
65+
matched, err := filepath.Match(request.Source.IgnoreHeadBranches, p.PullRequestObject.HeadRefName)
66+
if err != nil {
67+
return nil, fmt.Errorf("failed to apply ignore head branch filter: %s", err)
68+
}
69+
if matched {
70+
continue
71+
}
72+
}
73+
5274
// Filter out pull request if it does not contain at least one of the desired labels
5375
if len(request.Source.Labels) > 0 {
5476
labelFound := false

check_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,36 @@ func TestCheck(t *testing.T) {
262262
resource.NewVersion(testPullRequests[10]),
263263
},
264264
},
265+
266+
{
267+
description: "check correctly ignores PRs that do not match the head branch filter",
268+
source: resource.Source{
269+
Repository: "itsdalmo/test-repository",
270+
AccessToken: "oauthtoken",
271+
HeadBranches: "pr8*",
272+
},
273+
version: resource.Version{},
274+
pullRequests: testPullRequests,
275+
files: [][]string{},
276+
expected: resource.CheckResponse{
277+
resource.NewVersion(testPullRequests[7]),
278+
},
279+
},
280+
281+
{
282+
description: "check correctly ignores PRs that do match the ignore head branch filter",
283+
source: resource.Source{
284+
Repository: "itsdalmo/test-repository",
285+
AccessToken: "oauthtoken",
286+
IgnoreHeadBranches: "pr2*",
287+
},
288+
version: resource.Version{},
289+
pullRequests: testPullRequests,
290+
files: [][]string{},
291+
expected: resource.CheckResponse{
292+
resource.NewVersion(testPullRequests[2]),
293+
},
294+
},
265295
}
266296

267297
for _, tc := range tests {

e2e/e2e_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,68 @@ func TestCheckE2E(t *testing.T) {
173173
resource.Version{PR: targetPullRequestID, Commit: targetCommitID, CommittedDate: targetDateTime},
174174
},
175175
},
176+
177+
{
178+
description: "check returns latest PR that matches the head branch filter",
179+
source: resource.Source{
180+
Repository: "itsdalmo/test-repository",
181+
AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"),
182+
V3Endpoint: "https://api.github.com/",
183+
V4Endpoint: "https://api.github.com/graphql",
184+
HeadBranches: "my*",
185+
DisableCISkip: true,
186+
},
187+
version: resource.Version{},
188+
expected: resource.CheckResponse{
189+
resource.Version{PR: targetPullRequestID, Commit: targetCommitID, CommittedDate: targetDateTime},
190+
},
191+
},
192+
193+
{
194+
description: "check works when head branch filter doesn't match any PRs",
195+
source: resource.Source{
196+
Repository: "itsdalmo/test-repository",
197+
AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"),
198+
V3Endpoint: "https://api.github.com/",
199+
V4Endpoint: "https://api.github.com/graphql",
200+
HeadBranches: "feature/*",
201+
DisableCISkip: true,
202+
},
203+
version: resource.Version{},
204+
expected: resource.CheckResponse(nil),
205+
},
206+
207+
{
208+
description: "check returns latest PR that matches the ignore head branch filter",
209+
source: resource.Source{
210+
Repository: "itsdalmo/test-repository",
211+
AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"),
212+
V3Endpoint: "https://api.github.com/",
213+
V4Endpoint: "https://api.github.com/graphql",
214+
IgnoreHeadBranches: "test*",
215+
DisableCISkip: true,
216+
},
217+
version: resource.Version{},
218+
expected: resource.CheckResponse{
219+
resource.Version{PR: targetPullRequestID, Commit: targetCommitID, CommittedDate: targetDateTime},
220+
},
221+
},
222+
223+
{
224+
description: "check works when ignore head branch filter doesn't match any PRs",
225+
source: resource.Source{
226+
Repository: "itsdalmo/test-repository",
227+
AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"),
228+
V3Endpoint: "https://api.github.com/",
229+
V4Endpoint: "https://api.github.com/graphql",
230+
IgnoreHeadBranches: "feature/*",
231+
DisableCISkip: true,
232+
},
233+
version: resource.Version{},
234+
expected: resource.CheckResponse{
235+
resource.Version{PR: developPullRequestID, Commit: developCommitID, CommittedDate: developDateTime},
236+
},
237+
},
176238
}
177239

178240
for _, tc := range tests {

models.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type Source struct {
2424
IgnoreDrafts bool `json:"ignore_drafts"`
2525
GitCryptKey string `json:"git_crypt_key"`
2626
BaseBranch string `json:"base_branch"`
27+
HeadBranches string `json:"head_branches"`
28+
IgnoreHeadBranches string `json:"ignore_head_branches"`
2729
RequiredReviewApprovals int `json:"required_review_approvals"`
2830
Labels []string `json:"labels"`
2931
States []githubv4.PullRequestState `json:"states"`

0 commit comments

Comments
 (0)