1+ # This workflow enforces GitFlow branch patterns by:
2+ # - Ensuring only hotfix/* branches can target main (release branches are handled automatically)
3+ # - Adding labels and comments to non-compliant PRs
4+ # - Automatically cleaning up when PRs are updated to comply
5+
6+ name : GitFlow | Check PR Branch Pattern
7+
8+ on :
9+ pull_request_target :
10+ types :
11+ - opened
12+ - reopened
13+ - synchronize
14+ - edited
15+
16+ # Add explicit permissions
17+ permissions :
18+ pull-requests : write
19+ issues : write
20+ contents : read
21+
22+ env :
23+ MAIN_BRANCH : " main"
24+ DEVELOP_BRANCH : " develop"
25+ VALID_PATTERNS : " ^(release|hotfix)/"
26+ LABEL_NAME : " invalid-branch"
27+ ERROR_MESSAGE_IDENTIFIER : " Invalid Branch Pattern for main Branch"
28+
29+ jobs :
30+ check_branch :
31+ runs-on : ubuntu-latest
32+ steps :
33+ # Step 1: Use GitHub Script for branch pattern validation (safer than shell)
34+ - name : Check branch pattern
35+ id : branch_check
36+ uses : actions/github-script@v7
37+ with :
38+ github-token : ${{ secrets.GITHUB_TOKEN }}
39+ result-encoding : string
40+ script : |
41+ // Get branch information from context
42+ const headRef = context.payload.pull_request.head.ref;
43+ const baseRef = context.payload.pull_request.base.ref;
44+ const mainBranch = process.env.MAIN_BRANCH;
45+ const validPattern = new RegExp(process.env.VALID_PATTERNS);
46+
47+ console.log(`Checking PR from '${headRef}' to '${baseRef}'`);
48+
49+ // Perform the validation in JavaScript instead of shell
50+ if (baseRef === mainBranch) {
51+ if (!validPattern.test(headRef)) {
52+ console.log(`::error::❌ Invalid branch! PRs to main must come from hotfix/* or release/* branches. Please target the develop branch instead.`);
53+ return 'invalid';
54+ } else {
55+ console.log(`::notice::✅ Branch pattern is valid: '${headRef}' → '${mainBranch}'`);
56+ return 'valid';
57+ }
58+ } else {
59+ console.log(`::notice::✅ Not targeting main branch, no pattern restrictions apply.`);
60+ return 'not-main';
61+ }
62+
63+ # Step 2: If the branch pattern is invalid, add a label and comment to the PR
64+ - name : Handle invalid branch (label + comment)
65+ if : steps.branch_check.outputs.result == 'invalid'
66+ uses : actions/github-script@v7
67+ with :
68+ github-token : ${{ secrets.GITHUB_TOKEN }}
69+ script : |
70+ const { owner, repo } = context.repo;
71+ const issue_number = context.payload.pull_request.number;
72+ const label = process.env.LABEL_NAME;
73+ const messageIdentifier = process.env.ERROR_MESSAGE_IDENTIFIER;
74+
75+ // Escape special characters in the message identifier for safer comparisons
76+ const escapedMessageIdentifier = messageIdentifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77+
78+ const message = `❌ **${messageIdentifier}**
79+
80+ According to our GitFlow workflow:
81+ - Pull requests to the \`main\` branch are only allowed from \`hotfix/*\` branches
82+ - Regular feature development and other changes should target the \`develop\` branch
83+
84+ 📝 **Action required:** Please update your PR to target the \`develop\` branch instead.
85+
86+ For more details about our contribution workflow, please refer to our [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md) guide.`;
87+
88+ // First step: Always apply the label
89+ console.log("Adding invalid-branch label to PR");
90+ try {
91+ await github.rest.issues.addLabels({
92+ owner,
93+ repo,
94+ issue_number,
95+ labels: [label]
96+ });
97+ } catch (e) {
98+ // In case label already exists or other error
99+ console.log(`Note: Could not add label: ${e.message}`);
100+ }
101+
102+ // Second step: Add comment if it doesn't exist
103+ const { data: comments } = await github.rest.issues.listComments({
104+ owner,
105+ repo,
106+ issue_number
107+ });
108+
109+ // Use regex test instead of includes for safer comparison
110+ const commentExists = comments.some(comment =>
111+ comment.body && new RegExp(escapedMessageIdentifier).test(comment.body)
112+ );
113+
114+ if (!commentExists) {
115+ console.log("Adding comment to PR");
116+ await github.rest.issues.createComment({
117+ owner,
118+ repo,
119+ issue_number,
120+ body: message
121+ });
122+ } else {
123+ console.log("Comment already exists, skipping");
124+ }
125+
126+ # Step 3: If the branch pattern is corrected, remove label and comment
127+ - name : Clean up if branch is corrected
128+ if : steps.branch_check.outputs.result == 'valid' || steps.branch_check.outputs.result == 'not-main'
129+ uses : actions/github-script@v7
130+ with :
131+ github-token : ${{ secrets.GITHUB_TOKEN }}
132+ script : |
133+ const { owner, repo } = context.repo;
134+ const issue_number = context.payload.pull_request.number;
135+ const label = process.env.LABEL_NAME;
136+ const messageIdentifier = process.env.ERROR_MESSAGE_IDENTIFIER;
137+
138+ // Escape special characters in the message identifier
139+ const escapedMessageIdentifier = messageIdentifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
140+ const messageRegex = new RegExp(escapedMessageIdentifier);
141+
142+ try {
143+ // Check if the label is present and remove it
144+ const { data: labels } = await github.rest.issues.listLabelsOnIssue({
145+ owner,
146+ repo,
147+ issue_number
148+ });
149+
150+ if (labels.some(l => l.name === label)) {
151+ console.log("Removing invalid-branch label from PR");
152+ await github.rest.issues.removeLabel({
153+ owner,
154+ repo,
155+ issue_number,
156+ name: label
157+ });
158+ } else {
159+ console.log("No label to remove");
160+ }
161+
162+ // Check existing comments and remove any invalid branch comments
163+ const { data: comments } = await github.rest.issues.listComments({
164+ owner,
165+ repo,
166+ issue_number
167+ });
168+
169+ // Find and delete any invalid branch comment
170+ for (const comment of comments) {
171+ // Use regex test instead of includes for safer comparison
172+ if (comment.body && messageRegex.test(comment.body)) {
173+ console.log(`Deleting comment ID: ${comment.id}`);
174+ await github.rest.issues.deleteComment({
175+ owner,
176+ repo,
177+ comment_id: comment.id
178+ });
179+ }
180+ }
181+ } catch (error) {
182+ console.log(`Error in cleanup: ${error}`);
183+ }
0 commit comments