Skip to content

Commit 59e2cfc

Browse files
committed
Enforce PR title to include Jira ticket
1 parent d90b680 commit 59e2cfc

5 files changed

Lines changed: 186 additions & 23 deletions

File tree

.github/workflows/build-and-test.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ on:
1616
testrail-run-id:
1717
description: The TestRail run ID
1818
type: string
19+
default: "0"
1920
secrets:
2021
SLACK_WEBHOOK:
2122
description: Slack Notifier Incoming Webhook
@@ -25,10 +26,10 @@ on:
2526
required: true
2627
TESTRAIL_USERNAME:
2728
description: TestRail username
28-
required: true
29+
required: false
2930
TESTRAIL_API_KEY:
3031
description: TestRail API key
31-
required: true
32+
required: false
3233
jobs:
3334
build-and-test:
3435
runs-on: ubuntu-latest

.github/workflows/ci.yaml

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,31 @@ on:
1919

2020
permissions: write-all
2121
jobs:
22-
# Create TestRail run
23-
create-testrail-run:
24-
name: Create TestRail Run
25-
uses: ./.github/workflows/create-testrail-run.yaml
26-
secrets:
27-
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
28-
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
22+
23+
# PR Lint
24+
pr-lint:
25+
name: PR Lint
26+
uses: ./.github/workflows/pr-lint.yaml
2927

3028
# Build and run unit tests
3129
build-and-test:
3230
name: Build and test
3331
uses: ./.github/workflows/build-and-test.yaml
34-
needs: create-testrail-run
32+
needs: pr-lint
3533
secrets:
3634
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
3735
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
38-
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
39-
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
40-
with:
41-
testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}}
4236

4337
# Run integration tests on emulators
4438
integration-tests:
4539
name: Integration tests
4640
uses: ./.github/workflows/integration-tests.yaml
4741
if: ${{ vars.ENABLE_INTEGRATION_TESTS == 'true' }}
48-
needs: create-testrail-run
42+
needs: pr-lint
4943
secrets:
5044
E2E_CONFIG: ${{ secrets.E2E_CONFIG }}
5145
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
5246
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
53-
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
54-
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
55-
with:
56-
testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}}
5747

5848
# Build and sign BrowserStack test artifacts
5949
# Skip this step for PRs created by dependabot
@@ -164,6 +154,7 @@ jobs:
164154
mend-sca-scan:
165155
name: Mend SCA Scan
166156
uses: ./.github/workflows/mend-sca-scan.yaml
157+
needs: pr-lint
167158
secrets:
168159
MEND_EMAIL: ${{ secrets.MEND_EMAIL }}
169160
MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }}
@@ -173,6 +164,7 @@ jobs:
173164
mend-sast-scan:
174165
name: Mend SAST Scan
175166
uses: ./.github/workflows/mend-sast-scan.yaml
167+
needs: pr-lint
176168
secrets:
177169
MEND_EMAIL: ${{ secrets.MEND_EMAIL }}
178170
MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }}

.github/workflows/integration-tests.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ on:
1616
testrail-run-id:
1717
description: TestRail Run ID
1818
type: string
19+
default: "0"
1920
secrets:
2021
SLACK_WEBHOOK:
2122
description: Slack Notifier Incoming Webhook
@@ -25,10 +26,10 @@ on:
2526
required: true
2627
TESTRAIL_USERNAME:
2728
description: TestRail username
28-
required: true
29+
required: false
2930
TESTRAIL_API_KEY:
3031
description: TestRail API key
31-
required: true
32+
required: false
3233
E2E_CONFIG:
3334
description: 'Variables for the e2e tests'
3435
required: true

.github/workflows/pr-lint.yaml

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
name: PR Lint
2+
3+
on:
4+
workflow_call:
5+
6+
jobs:
7+
pr-lint:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Validate PR title + description + commit signatures
11+
uses: actions/github-script@v7
12+
with:
13+
script: |
14+
const pr = context.payload.pull_request;
15+
const title = (pr.title || "").trim();
16+
const body = (pr.body || "").trim();
17+
18+
// 1) Title pattern: SDKS-1234 <something>
19+
// - allow multiple spaces, but require at least one space after ticket
20+
const titleRe = /^SDKS-\d+\s+\S.+/;
21+
if (!titleRe.test(title)) {
22+
core.setFailed(
23+
`PR title must match "SDKS-#### <description>". Got: "${title}"`
24+
);
25+
return;
26+
}
27+
28+
// 2) Description exists
29+
if (!body || body.length < 10) {
30+
core.setFailed("PR description must be present and meaningful (>= 10 chars).");
31+
return;
32+
}
33+
34+
// 3) All commits are signed (GitHub verification)
35+
// Note: "verified" is GitHub's verification result for the commit signature.
36+
const { owner, repo } = context.repo;
37+
38+
// paginate commits in PR
39+
const commits = await github.paginate(
40+
github.rest.pulls.listCommits,
41+
{ owner, repo, pull_number: pr.number, per_page: 100 }
42+
);
43+
44+
const failures = [];
45+
for (const c of commits) {
46+
const sha = c.sha;
47+
// Fetch commit detail to reliably get verification state
48+
const commitResp = await github.rest.repos.getCommit({ owner, repo, ref: sha });
49+
const v = commitResp.data?.commit?.verification;
50+
51+
const verified = v?.verified === true;
52+
const reason = v?.reason || "unknown";
53+
54+
if (!verified) {
55+
failures.push(`${sha.substring(0, 7)} (${reason})`);
56+
}
57+
}
58+
59+
if (failures.length) {
60+
core.setFailed(
61+
`All commits must be signed (Verified). Unsigned/unverified commits:\n- ` +
62+
failures.join("\n- ")
63+
);
64+
return;
65+
}
66+
67+
core.info("PR metadata + commit signature checks passed.");
68+
69+
- name: Checkout PR head (for copyirght header checks)
70+
uses: actions/checkout@v4
71+
with:
72+
fetch-depth: 1
73+
74+
- name: Validate copyright headers on changed files
75+
env:
76+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77+
PR_NUMBER: ${{ github.event.pull_request.number }}
78+
REPO: ${{ github.repository }}
79+
run: |
80+
set -euo pipefail
81+
YEAR="$(date +'%Y')"
82+
echo "Current year: ${YEAR}"
83+
84+
python3 - << 'PY'
85+
import json, os, re, sys, subprocess
86+
87+
repo = os.environ["REPO"]
88+
pr_number = os.environ["PR_NUMBER"]
89+
year = subprocess.check_output(["date", "+%Y"], text=True).strip()
90+
91+
# Fetch changed files JSON (paginated) via gh
92+
cmd = [
93+
"gh","api",
94+
"-H","Accept: application/vnd.github+json",
95+
f"/repos/{repo}/pulls/{pr_number}/files?per_page=100",
96+
"--paginate"
97+
]
98+
raw = subprocess.check_output(cmd, text=True).strip()
99+
if not raw:
100+
print("❌ gh api returned empty response; cannot validate files.")
101+
sys.exit(1)
102+
103+
files = json.loads(raw)
104+
105+
skip_ext = {
106+
".png",".jpg",".jpeg",".gif",".webp",".ico",
107+
".pdf",".zip",".jar",".aar",".so",".dylib",
108+
".keystore",".jks",".p12",".mobileprovision",
109+
".ttf",".otf",".mp4",".mov",".wav",".mp3",
110+
".lock"
111+
}
112+
skip_paths_re = re.compile(
113+
r"^(?:\.github/|\.idea/|\.gradle/|build/|dist/|DerivedData/|Pods/|Carthage/|\.swiftpm/|node_modules/)"
114+
)
115+
116+
# Adjust this to your canonical header text.
117+
header_re = re.compile(
118+
rf"Copyright\s*\(c\)\s*(?:\d{{4}}\s*-\s*)?{re.escape(year)}\s+Ping Identity Corporation\.",
119+
re.IGNORECASE
120+
)
121+
122+
bad = []
123+
checked = []
124+
125+
for f in files:
126+
status = f.get("status")
127+
path = f.get("filename")
128+
if status == "removed" or not path:
129+
continue
130+
if skip_paths_re.search(path):
131+
continue
132+
133+
ext = "." + path.split(".")[-1].lower() if "." in path else ""
134+
if ext in skip_ext:
135+
continue
136+
137+
if status not in ("added","modified","renamed"):
138+
continue
139+
140+
try:
141+
data = open(path, "rb").read()
142+
except FileNotFoundError:
143+
# If renamed, the new path should exist; if not, skip.
144+
continue
145+
146+
# Skip binary-ish
147+
if b"\x00" in data[:4096]:
148+
continue
149+
150+
text = data.decode("utf-8", errors="replace")
151+
head = text[:3000]
152+
153+
checked.append(path)
154+
if not header_re.search(head):
155+
bad.append(path)
156+
157+
if bad:
158+
print("\n❌ Copyright header check failed.")
159+
print(f"Expected current year {year} in header for these changed files:")
160+
for p in bad:
161+
print(f" - {p}")
162+
print("\nExpected header examples:")
163+
print(f" Copyright (c) {year} Ping Identity Corporation. All rights reserved.")
164+
print(f" Copyright (c) 2024-{year} Ping Identity Corporation. All rights reserved.")
165+
sys.exit(1)
166+
167+
print(f"\n✅ Copyright header check passed for {len(checked)} file(s).")
168+
PY
169+
shell: bash

foundation/android/src/main/kotlin/com/pingidentity/android/ContextProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved.
2+
* Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved.
33
*
44
* This software may be modified and distributed under the terms
55
* of the MIT license. See the LICENSE file for details.
66
*/
77

8-
package com.pingidentity.android
8+
package com.pingidentity.android
99

1010
import android.annotation.SuppressLint
1111
import android.app.Activity

0 commit comments

Comments
 (0)