mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
2a077cae5e
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io> Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com> Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
177 lines
7.8 KiB
YAML
177 lines
7.8 KiB
YAML
name: 'Tools: PR Conflict Checker'
|
||
|
||
on:
|
||
# zizmor: ignore[dangerous-triggers] - intentional: needs write access for conflict labels/comments, checkout uses PR head SHA for read-only grep
|
||
pull_request_target:
|
||
types:
|
||
- 'opened'
|
||
- 'synchronize'
|
||
- 'reopened'
|
||
branches:
|
||
- 'master'
|
||
- 'v5.*'
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||
cancel-in-progress: true
|
||
|
||
permissions: {}
|
||
|
||
jobs:
|
||
check-conflicts:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 15
|
||
permissions:
|
||
contents: read
|
||
pull-requests: write
|
||
issues: write
|
||
|
||
steps:
|
||
- name: Harden the runner (Audit all outbound calls)
|
||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||
with:
|
||
egress-policy: audit
|
||
|
||
- name: Checkout PR head
|
||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
with:
|
||
ref: ${{ github.event.pull_request.head.sha }}
|
||
fetch-depth: 1
|
||
persist-credentials: false # No write token in the untrusted PR-head tree; public repo so base fetch/changed-files work unauthenticated
|
||
|
||
- name: Fetch PR base ref for tj-actions/changed-files
|
||
env:
|
||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||
run: git fetch --depth=1 origin "${BASE_REF}"
|
||
|
||
- name: Get changed files
|
||
id: changed-files
|
||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||
with:
|
||
files: '**'
|
||
safe_output: false # Raw paths (list read via env var, injection-safe); default escaping backslash-quotes chars like () and breaks the -f test
|
||
separator: "\n" # Newline-delimited so the reader tolerates spaces and glob chars in paths
|
||
|
||
- name: Check for conflict markers
|
||
id: conflict-check
|
||
run: |
|
||
echo "Checking for conflict markers in changed files..."
|
||
|
||
CONFLICT_FILES=""
|
||
HAS_CONFLICTS=false
|
||
|
||
# Read newline-delimited paths so spaces/globs neither word-split nor glob-expand
|
||
while IFS= read -r file; do
|
||
[ -n "$file" ] || continue
|
||
[ -f "$file" ] || continue
|
||
echo "Checking file: $file"
|
||
|
||
if grep -qE '^(<<<<<<<|=======|>>>>>>>)' "$file" 2>/dev/null; then
|
||
echo "Conflict markers found in: $file"
|
||
CONFLICT_FILES="${CONFLICT_FILES}- \`${file}\`"$'\n'
|
||
HAS_CONFLICTS=true
|
||
fi
|
||
done <<< "$STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES"
|
||
|
||
if [ "$HAS_CONFLICTS" = true ]; then
|
||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||
{
|
||
echo "conflict_files<<EOF"
|
||
echo "$CONFLICT_FILES"
|
||
echo "EOF"
|
||
} >> $GITHUB_OUTPUT
|
||
echo "Conflict markers detected"
|
||
else
|
||
echo "has_conflicts=false" >> $GITHUB_OUTPUT
|
||
echo "No conflict markers found in changed files"
|
||
fi
|
||
env:
|
||
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||
|
||
- name: Check base-branch mergeability
|
||
id: merge-check
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||
REPO: ${{ github.repository }}
|
||
run: |
|
||
MERGEABLE=null
|
||
|
||
# GitHub computes mergeability async, so .mergeable is null until ready; poll until resolved
|
||
for attempt in 1 2 3 4 5; do
|
||
MERGEABLE=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.mergeable')
|
||
if [ "$MERGEABLE" != "null" ]; then
|
||
break
|
||
fi
|
||
echo "Attempt ${attempt}: mergeability not computed yet, retrying..."
|
||
sleep 3
|
||
done
|
||
|
||
# Keep 'unknown' distinct from 'clean' so we never assert a clean merge we could not confirm
|
||
case "$MERGEABLE" in
|
||
false) STATUS=conflict; echo "PR branch cannot be merged cleanly into its base branch" ;;
|
||
true) STATUS=clean; echo "PR branch merges cleanly into its base branch" ;;
|
||
*) STATUS=unknown; echo "::warning::Mergeability did not resolve after retries; leaving it undetermined" ;;
|
||
esac
|
||
|
||
echo "merge_status=${STATUS}" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Manage conflict label
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||
HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }}
|
||
MERGE_STATUS: ${{ steps.merge-check.outputs.merge_status }}
|
||
run: |
|
||
LABEL_NAME="has-conflicts"
|
||
|
||
if [ "$HAS_CONFLICTS" = "true" ] || [ "$MERGE_STATUS" = "conflict" ]; then
|
||
echo "Adding conflict label to PR #${PR_NUMBER}..."
|
||
gh pr edit "$PR_NUMBER" --add-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
||
elif [ "$MERGE_STATUS" = "unknown" ]; then
|
||
# Don't drop the label on an undetermined merge state; a later run will settle it
|
||
echo "Mergeability undetermined; leaving label unchanged"
|
||
else
|
||
echo "Removing conflict label from PR #${PR_NUMBER}..."
|
||
gh pr edit "$PR_NUMBER" --remove-label "$LABEL_NAME" --repo ${{ github.repository }} || true
|
||
fi
|
||
|
||
- name: Find existing comment
|
||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||
id: find-comment
|
||
with:
|
||
issue-number: ${{ github.event.pull_request.number }}
|
||
comment-author: 'github-actions[bot]'
|
||
body-includes: '<!-- conflict-checker-comment -->'
|
||
|
||
- name: Create or update comment
|
||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||
with:
|
||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||
issue-number: ${{ github.event.pull_request.number }}
|
||
edit-mode: replace
|
||
body: |
|
||
<!-- conflict-checker-comment -->
|
||
${{ (steps.conflict-check.outputs.has_conflicts == 'true' || steps.merge-check.outputs.merge_status == 'conflict') && '⚠️ **Conflicts Detected**' || (steps.merge-check.outputs.merge_status == 'unknown' && 'ℹ️ **Conflict Check Incomplete**' || '✅ **No Conflicts**') }}
|
||
${{ steps.conflict-check.outputs.has_conflicts == 'true' && format('
|
||
**Conflict markers** are present in the following files:
|
||
|
||
{0}
|
||
Resolve them by removing every `<<<<<<<`, `=======`, and `>>>>>>>` marker, then commit and push.', steps.conflict-check.outputs.conflict_files) || '' }}
|
||
${{ steps.merge-check.outputs.merge_status == 'conflict' && '
|
||
**Merge conflict with the base branch.** This PR cannot be merged cleanly. Update your branch with the latest base (rebase or merge) and resolve the conflicts.' || '' }}
|
||
${{ steps.merge-check.outputs.merge_status == 'unknown' && '
|
||
GitHub had not finished computing mergeability, so base-branch conflict status could not be verified on this run.' || '' }}
|
||
${{ (steps.conflict-check.outputs.has_conflicts != 'true' && steps.merge-check.outputs.merge_status == 'clean') && '
|
||
No conflict markers, and the branch merges cleanly into its base.' || '' }}
|
||
|
||
- name: Fail workflow if conflicts detected
|
||
if: steps.conflict-check.outputs.has_conflicts == 'true' || steps.merge-check.outputs.merge_status == 'conflict'
|
||
env:
|
||
HAS_CONFLICTS: ${{ steps.conflict-check.outputs.has_conflicts }}
|
||
MERGE_STATUS: ${{ steps.merge-check.outputs.merge_status }}
|
||
run: |
|
||
[ "$HAS_CONFLICTS" = "true" ] && echo "::error::Conflict markers detected in changed files"
|
||
[ "$MERGE_STATUS" = "conflict" ] && echo "::error::PR branch has merge conflicts with the base branch"
|
||
exit 1
|