Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/Check-pull-request-source-branch.yml

This file was deleted.

File renamed without changes.
183 changes: 183 additions & 0 deletions .github/workflows/GitFlow_Check-pull-request-source-branch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# This workflow enforces GitFlow branch patterns by:
# - Ensuring only hotfix/* branches can target main (release branches are handled automatically)
# - Adding labels and comments to non-compliant PRs
# - Automatically cleaning up when PRs are updated to comply

name: GitFlow | Check PR Branch Pattern

on:
pull_request_target:
types:
- opened
- reopened
- synchronize
- edited

# Add explicit permissions
permissions:
pull-requests: write
issues: write
contents: read

env:
MAIN_BRANCH: "main"
DEVELOP_BRANCH: "develop"
VALID_PATTERNS: "^(release|hotfix)/"
LABEL_NAME: "invalid-branch"
ERROR_MESSAGE_IDENTIFIER: "Invalid Branch Pattern for main Branch"

jobs:
check_branch:
runs-on: ubuntu-latest
steps:
# Step 1: Use GitHub Script for branch pattern validation (safer than shell)
- name: Check branch pattern
id: branch_check
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
// Get branch information from context
const headRef = context.payload.pull_request.head.ref;
const baseRef = context.payload.pull_request.base.ref;
const mainBranch = process.env.MAIN_BRANCH;
const validPattern = new RegExp(process.env.VALID_PATTERNS);

console.log(`Checking PR from '${headRef}' to '${baseRef}'`);

// Perform the validation in JavaScript instead of shell
if (baseRef === mainBranch) {
if (!validPattern.test(headRef)) {
console.log(`::error::❌ Invalid branch! PRs to main must come from hotfix/* or release/* branches. Please target the develop branch instead.`);
return 'invalid';
} else {
console.log(`::notice::✅ Branch pattern is valid: '${headRef}' → '${mainBranch}'`);
return 'valid';
}
} else {
console.log(`::notice::✅ Not targeting main branch, no pattern restrictions apply.`);
return 'not-main';
}

# Step 2: If the branch pattern is invalid, add a label and comment to the PR
- name: Handle invalid branch (label + comment)
if: steps.branch_check.outputs.result == 'invalid'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const label = process.env.LABEL_NAME;
const messageIdentifier = process.env.ERROR_MESSAGE_IDENTIFIER;

// Escape special characters in the message identifier for safer comparisons
const escapedMessageIdentifier = messageIdentifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

const message = `❌ **${messageIdentifier}**

According to our GitFlow workflow:
- Pull requests to the \`main\` branch are only allowed from \`hotfix/*\` branches
- Regular feature development and other changes should target the \`develop\` branch

📝 **Action required:** Please update your PR to target the \`develop\` branch instead.

For more details about our contribution workflow, please refer to our [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md) guide.`;

// First step: Always apply the label
console.log("Adding invalid-branch label to PR");
try {
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels: [label]
});
} catch (e) {
// In case label already exists or other error
console.log(`Note: Could not add label: ${e.message}`);
}

// Second step: Add comment if it doesn't exist
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number
});

// Use regex test instead of includes for safer comparison
const commentExists = comments.some(comment =>
comment.body && new RegExp(escapedMessageIdentifier).test(comment.body)
);

if (!commentExists) {
console.log("Adding comment to PR");
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message
});
} else {
console.log("Comment already exists, skipping");
}

# Step 3: If the branch pattern is corrected, remove label and comment
- name: Clean up if branch is corrected
if: steps.branch_check.outputs.result == 'valid' || steps.branch_check.outputs.result == 'not-main'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const label = process.env.LABEL_NAME;
const messageIdentifier = process.env.ERROR_MESSAGE_IDENTIFIER;

// Escape special characters in the message identifier
const escapedMessageIdentifier = messageIdentifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const messageRegex = new RegExp(escapedMessageIdentifier);

try {
// Check if the label is present and remove it
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number
});

if (labels.some(l => l.name === label)) {
console.log("Removing invalid-branch label from PR");
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name: label
});
} else {
console.log("No label to remove");
}

// Check existing comments and remove any invalid branch comments
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number
});

// Find and delete any invalid branch comment
for (const comment of comments) {
// Use regex test instead of includes for safer comparison
if (comment.body && messageRegex.test(comment.body)) {
console.log(`Deleting comment ID: ${comment.id}`);
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: comment.id
});
}
}
} catch (error) {
console.log(`Error in cleanup: ${error}`);
}
71 changes: 71 additions & 0 deletions .github/workflows/GitFlow_Create-Release-Branch-and-PR.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This workflow implements the GitFlow release creation process:
# - Creates a new release branch from develop
# - Increments version according to semantic versioning
# - Creates a pull request targeting main branch
# - This enables final testing before the actual release is created

name: GitFlow | Create Release Branch and PR

on:
# Manual trigger with version selection
workflow_dispatch:
inputs:
version:
type: choice
default: "Minor"
description: Select next release type
options:
- Patch # For bug fixes (x.y.Z)
- Minor # For new features (x.Y.z)
- Major # For breaking changes (X.y.z)
required: true

jobs:
create-release:
runs-on: ubuntu-latest
steps:
# Step 1: Calculate the next version number based on the selected increment type
- name: Auto Increment Semver Action
uses: MCKanpolat/auto-semver-action@5003b8d37f4b03d95f15303ea10242cbf7c13141 # 2
id: versioning
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
incrementPerCommit: false
releaseType: ${{ github.event.inputs.version }}

# Step 2: Checkout the develop branch which contains all approved features
- name: Checkout code
uses: actions/[email protected]
with:
ref: develop # Always create releases from develop branch
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

# Step 3: Create a new release branch following GitFlow naming convention
- name: Create release branch
run: |
# Configure Git with GitHub Actions identity
git config --global user.name "GitHub Actions"
git config --global user.email "[email protected]"

# Create a new branch with the pattern release/X.Y.Z
git checkout -b release/${{ steps.versioning.outputs.version }}

# Push the branch to remote repository
git push origin release/${{ steps.versioning.outputs.version }}
echo "Release Branch: release/${{ steps.versioning.outputs.version }}"

# Step 4: Create a pull request from release branch to main
- name: Create Pull Request with GitHub CLI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Use GitHub CLI to create a properly formatted Pull Request
gh pr create \
--base main \
--head release/${{ steps.versioning.outputs.version }} \
--title "Release ${{ steps.versioning.outputs.version }}" \
--label "release" \
--body "# Release ${{ steps.versioning.outputs.version }}

This PR is automatically created to perform the final tests and validations before the release is created."
Loading