Compare commits
1 Commits
fix/cleanu
...
burn/1500-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b9672d82b |
89
docs/duplicate-pr-prevention.md
Normal file
89
docs/duplicate-pr-prevention.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Duplicate PR Prevention System
|
||||
|
||||
## Overview
|
||||
|
||||
The Nexus uses a multi-layer system to prevent duplicate PRs for the same issue.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Pre-flight Check (CI)
|
||||
|
||||
The `.github/workflows/pr-duplicate-check.yml` workflow runs on every PR creation and checks if a PR already exists for the same issue.
|
||||
|
||||
**How it works:**
|
||||
1. Extracts issue numbers from PR title and body
|
||||
2. Queries Gitea API for existing PRs referencing those issues
|
||||
3. Fails the check if duplicates are found
|
||||
4. Provides links to existing PRs for review
|
||||
|
||||
### 2. Cleanup Script
|
||||
|
||||
The `scripts/cleanup-duplicate-prs.sh` script helps clean up existing duplicates:
|
||||
- Lists all PRs for a given issue
|
||||
- Identifies duplicates
|
||||
- Provides commands to close duplicates
|
||||
|
||||
### 3. Milestone Checker
|
||||
|
||||
The `bin/check_duplicate_milestones.py` script prevents duplicate milestones:
|
||||
- Scans all milestones in the repo
|
||||
- Identifies duplicates by title
|
||||
- Reports for manual cleanup
|
||||
|
||||
## Usage
|
||||
|
||||
### Check for Duplicates Before Creating PR
|
||||
|
||||
```bash
|
||||
# Check if issue already has PRs
|
||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://forge.alexanderwhitestone.com/api/v1/repos/Timmy_Foundation/the-nexus/pulls?state=open" \
|
||||
| jq '.[] | select(.body | contains("#ISSUE_NUMBER"))'
|
||||
```
|
||||
|
||||
### Clean Up Existing Duplicates
|
||||
|
||||
```bash
|
||||
# List PRs for issue
|
||||
./scripts/cleanup-duplicate-prs.sh --issue 1128
|
||||
|
||||
# Close duplicates (keep newest)
|
||||
./scripts/cleanup-duplicate-prs.sh --issue 1128 --close-duplicates
|
||||
```
|
||||
|
||||
## Example: Issue #1500
|
||||
|
||||
Issue #1500 documented that the pre-flight check successfully prevented a duplicate PR for #1474.
|
||||
|
||||
**What happened:**
|
||||
1. Dispatch attempted to work on #1474
|
||||
2. Pre-flight check found 2 existing PRs (#1495, #1493)
|
||||
3. System prevented creating a 3rd duplicate
|
||||
4. Issue #1500 was filed as an observation
|
||||
|
||||
**Result:** The system worked as intended.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check before creating PRs** — use the pre-flight check
|
||||
2. **Close duplicates promptly** — don't let them accumulate
|
||||
3. **Reference issues in PRs** — makes duplicate detection possible
|
||||
4. **Use descriptive branch names** — helps identify purpose
|
||||
5. **Review existing PRs first** — don't assume you're the first
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Duplicate PR detected" error
|
||||
|
||||
This means a PR already exists for the issue. Options:
|
||||
1. Review the existing PR and contribute to it
|
||||
2. Close your PR if it's truly a duplicate
|
||||
3. Update your PR to address a different aspect
|
||||
|
||||
### Pre-flight check not running
|
||||
|
||||
Check that `.github/workflows/pr-duplicate-check.yml` exists and is enabled.
|
||||
|
||||
### False positives
|
||||
|
||||
The check looks for issue numbers in PR body. If you're referencing an issue without intending to fix it, use "Refs #" instead of "Fixes #".
|
||||
@@ -168,62 +168,3 @@ else
|
||||
fi
|
||||
|
||||
log "Script complete"
|
||||
|
||||
# ─── Stale Branch Cleanup ─────────────────────────────────
|
||||
# Clean up branches from closed (unmerged) PRs and merged PRs
|
||||
log "Checking for stale branches from closed/merged PRs..."
|
||||
|
||||
# Get all open PRs to avoid deleting active branches
|
||||
OPEN_BRANCHES=$(curl -s -H "$AUTH" "$API/repos/$REPO/pulls?state=open&limit=100" | jq -r '.[] | .head.ref' | sort -u)
|
||||
|
||||
# Get all closed PRs (last 100)
|
||||
CLOSED_PRS=$(curl -s -H "$AUTH" "$API/repos/$REPO/pulls?state=closed&limit=100")
|
||||
|
||||
if [ -n "$CLOSED_PRS" ] && [ "$CLOSED_PRS" != "null" ]; then
|
||||
STALE_BRANCHES=$(echo "$CLOSED_PRS" | jq -r '.[] | select(.merged == false) | .head.ref' | sort -u)
|
||||
MERGED_BRANCHES=$(echo "$CLOSED_PRS" | jq -r '.[] | select(.merged == true) | .head.ref' | sort -u)
|
||||
|
||||
STALE_COUNT=0
|
||||
for branch in $STALE_BRANCHES; do
|
||||
# Skip main/master/develop
|
||||
case "$branch" in main|master|develop|HEAD) continue ;; esac
|
||||
|
||||
# SAFETY CHECK: Skip if branch is still used by an open PR
|
||||
if echo "$OPEN_BRANCHES" | grep -q "^$branch$"; then
|
||||
log "Skipping branch '$branch' - still has an open PR"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
log "DRY RUN: Would delete stale branch '$branch' (from closed unmerged PR)"
|
||||
else
|
||||
curl -s -X DELETE -H "$AUTH" "$API/repos/$REPO/branches/$branch" > /dev/null 2>&1 || true
|
||||
log "Deleted stale branch: $branch"
|
||||
fi
|
||||
STALE_COUNT=$((STALE_COUNT + 1))
|
||||
done
|
||||
|
||||
MERGED_COUNT=0
|
||||
for branch in $MERGED_BRANCHES; do
|
||||
case "$branch" in main|master|develop|HEAD) continue ;; esac
|
||||
|
||||
if echo "$OPEN_BRANCHES" | grep -q "^$branch$"; then
|
||||
log "Skipping branch '$branch' - still has an open PR"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
log "DRY RUN: Would delete merged branch '$branch'"
|
||||
else
|
||||
curl -s -X DELETE -H "$AUTH" "$API/repos/$REPO/branches/$branch" > /dev/null 2>&1 || true
|
||||
log "Deleted merged branch: $branch"
|
||||
fi
|
||||
MERGED_COUNT=$((MERGED_COUNT + 1))
|
||||
done
|
||||
|
||||
log "Stale branch cleanup:"
|
||||
log " Closed (unmerged) branches: $STALE_COUNT"
|
||||
log " Merged branches: $MERGED_COUNT"
|
||||
else
|
||||
log "Could not fetch closed PRs for branch cleanup"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user