├── .github ├── ISSUE_TEMPLATE │ ├── blank.md │ ├── config.yml │ └── update-entry.md ├── pull_request_template.md ├── MILESTONES.md └── workflows │ ├── maintain-contribution-issues.yml │ ├── post-merge-process.yml │ └── process-contribution.yml ├── ADD_YOUR_NAME.md ├── scripts ├── reset-staging.js ├── process-contribution.js ├── update-badge.js ├── check-profanity.js ├── sort-contributors.js ├── validate-contribution.js ├── comprehensive-validation.js └── validation-utils.js ├── package.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── CONTRIBUTORS.md /.github/ISSUE_TEMPLATE/blank.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Questions, suggestions, or anything else 4 | title: '' 5 | labels: ['help'] 6 | assignees: ['SashankBhamidi'] 7 | --- 8 | 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📖 README 4 | url: https://github.com/SashankBhamidi/git-gang/blob/master/README.md 5 | about: Read the contribution guidelines and project info 6 | -------------------------------------------------------------------------------- /ADD_YOUR_NAME.md: -------------------------------------------------------------------------------- 1 | # Add Your Name 2 | 3 | Want to join the Git Gang? Just fill out the form below. 4 | 5 | **Note:** Name can be your real name or any alias/handle you prefer. 6 | 7 | **Don't forget to star this repository!** ⭐ 8 | 9 | ## Add your entry below this line 10 | 11 | - Name: 12 | - Username: 13 | - Message: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/update-entry.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update My Entry 3 | about: Update your existing Git Gang entry 4 | title: 'Update entry for @username' 5 | labels: ['help'] 6 | assignees: ['SashankBhamidi'] 7 | --- 8 | 9 | # Update My Entry 10 | 11 | ## What needs updating? 12 | 13 | - [ ] Update my message 14 | - [ ] Fix my GitHub username 15 | - [ ] Change my name 16 | - [ ] Remove my entry 17 | - [ ] Other 18 | 19 | ## Details 20 | 21 | Tell us your current username and what you want changed. -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Adding My Name to Git Gang 2 | 3 | > **Note**: This template is only for adding your name to the contributors list. For other changes, issues, or updates to existing entries, please create an issue instead. 4 | 5 | ## What I did 6 | 7 | I filled out the simple form in ADD_YOUR_NAME.md with my details. 8 | 9 | ## Checklist 10 | 11 | - [ ] I filled out Name, Username, and Message 12 | - [ ] This is my first contribution 13 | - [ ] I only modified ADD_YOUR_NAME.md 14 | - [ ] I starred this repository ⭐ (helps us reach more contributors!) 15 | 16 | --- 17 | 18 | Our automation will validate your entry and merge this PR if everything looks good. Welcome to the Git Gang! -------------------------------------------------------------------------------- /scripts/reset-staging.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | 5 | function resetStagingFile() { 6 | try { 7 | const template = `# Add Your Name 8 | 9 | Want to join the Git Gang? Just fill out the form below. 10 | 11 | **Note:** Name can be your real name or any alias/handle you prefer. 12 | 13 | **Don't forget to star this repository!** ⭐ 14 | 15 | ## Add your entry below this line 16 | 17 | - Name: 18 | - Username: 19 | - Message: `; 20 | 21 | fs.writeFileSync('ADD_YOUR_NAME.md', template); 22 | console.log('Staging file reset to template'); 23 | 24 | } catch (error) { 25 | console.error('Failed to reset staging file:', error.message); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | resetStagingFile(); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-gang", 3 | "version": "1.0.0", 4 | "description": "The world's biggest contributors list!", 5 | "scripts": { 6 | "validate": "node scripts/validate-contribution.js", 7 | "validate-comprehensive": "node scripts/comprehensive-validation.js", 8 | "process": "node scripts/process-contribution.js", 9 | "sort": "node scripts/sort-contributors.js", 10 | "update-badge": "node scripts/update-badge.js", 11 | "reset": "node scripts/reset-staging.js", 12 | "check-profanity": "node scripts/check-profanity.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/SashankBhamidi/git-gang.git" 17 | }, 18 | "keywords": [ 19 | "contributors", 20 | "community", 21 | "github", 22 | "open-source" 23 | ], 24 | "author": "", 25 | "license": "MIT", 26 | "devDependencies": {}, 27 | "engines": { 28 | "node": ">=18.0.0" 29 | } 30 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Git Gang 2 | 3 | Want to join the Git Gang? It takes 15 seconds. 4 | 5 | ## How to Add Your Name 6 | 7 | 1. **Open this file:** [ADD_YOUR_NAME.md](ADD_YOUR_NAME.md) 8 | 2. **Fill out the form:** 9 | ``` 10 | - Name: Your Name 11 | - Username: your-github-username 12 | - Message: Optional message 13 | ``` 14 | 3. **Submit a pull request** 15 | 4. **Done.** Our automation handles the rest. 16 | 17 | ## The Rules 18 | 19 | - One contribution per person 20 | - Use your real GitHub username 21 | - Keep your message respectful 22 | - Name can be real or alias 23 | 24 | ## Need Help? 25 | 26 | [Open an issue](https://github.com/SashankBhamidi/git-gang/issues/new/choose) and we'll help you out. 27 | 28 | ## Want to Update Your Entry? 29 | 30 | [Create an issue](https://github.com/SashankBhamidi/git-gang/issues/new?labels=help&template=update-entry.md&title=Update+my+entry) and we'll update it for you. 31 | -------------------------------------------------------------------------------- /scripts/process-contribution.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const { ENTRY_REGEX, validateFormat } = require('./validation-utils'); 5 | 6 | function addToContributors(entry) { 7 | try { 8 | let contributors = fs.readFileSync('CONTRIBUTORS.md', 'utf8'); 9 | 10 | contributors = contributors.trim() + '\n- ' + entry + '\n'; 11 | 12 | fs.writeFileSync('CONTRIBUTORS.md', contributors); 13 | 14 | } catch (error) { 15 | console.error('Failed to add entry to contributors:', error.message); 16 | process.exit(1); 17 | } 18 | } 19 | 20 | function main() { 21 | const entry = process.argv[2]; 22 | 23 | if (!entry) { 24 | console.error('No entry found to process'); 25 | process.exit(1); 26 | } 27 | 28 | // Validate entry format before processing 29 | const validation = validateFormat(entry); 30 | if (!validation.valid) { 31 | console.error(`Invalid entry format: ${validation.error}`); 32 | process.exit(1); 33 | } 34 | 35 | addToContributors(entry); 36 | console.log('Entry added to contributors!'); 37 | } 38 | 39 | main(); -------------------------------------------------------------------------------- /.github/MILESTONES.md: -------------------------------------------------------------------------------- 1 | # Contributor Milestones 2 | 3 | Tracking milestone contributors for Git Gang (by chronological join order). 4 | 5 | ## Milestones Reached 6 | 7 | | Milestone | Contributor | GitHub Username | PR # | 8 | |-----------|-------------|-----------------|------| 9 | | 1st | Sashank Bhamidi | [@SashankBhamidi](https://github.com/SashankBhamidi) | #1 | 10 | | 10th | Raunak | [@raunak-devs](https://github.com/raunak-devs) | #13 | 11 | | 25th | Mishraaa G | [@mishraa-G](https://github.com/mishraa-G) | #63 | 12 | | 50th | Sneha Chourey | [@Sneha-chourey](https://github.com/Sneha-chourey) | #132 | 13 | | 75th | Tanishq | [@TanishqDNEC](https://github.com/TanishqDNEC) | #203 | 14 | | 100th | Ansh Kumar | [@Akrodriguez](https://github.com/Akrodriguez) | #257 | 15 | 16 | ## Upcoming Milestones 17 | 18 | - 200th contributor 19 | - 500th contributor 20 | - 1,000th contributor 21 | - 2,000th contributor 22 | - 5,000th contributor 23 | - 10,000th contributor 24 | - 15,000th contributor 25 | - 20,000th contributor 26 | 27 | --- 28 | 29 | *Note: This file tracks milestones based on PR merge order, not alphabetical order in CONTRIBUTORS.md* 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sashank Bhamidi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /scripts/update-badge.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | 5 | function updateBadge() { 6 | try { 7 | // Read CONTRIBUTORS.md to get count 8 | const contributors = fs.readFileSync('CONTRIBUTORS.md', 'utf8'); 9 | const countMatch = contributors.match(/Total contributors: (\d+)/); 10 | const count = countMatch ? countMatch[1] : '0'; 11 | 12 | // Read README.md 13 | const readme = fs.readFileSync('README.md', 'utf8'); 14 | 15 | // Update the contributors badge 16 | const updatedReadme = readme.replace( 17 | /!\[Contributors\]\(https:\/\/img\.shields\.io\/badge\/contributors-\d+-brightgreen\.svg\?style=flat-square\)/, 18 | `![Contributors](https://img.shields.io/badge/contributors-${count}-brightgreen.svg?style=flat-square)` 19 | ); 20 | 21 | // Only write if content changed 22 | if (updatedReadme !== readme) { 23 | fs.writeFileSync('README.md', updatedReadme); 24 | console.log(`Badge updated to show ${count} contributors`); 25 | } else { 26 | console.log(`Badge already shows ${count} contributors (no update needed)`); 27 | } 28 | 29 | } catch (error) { 30 | console.error('Failed to update badge:', error.message); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | updateBadge(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Gang 2 | 3 | [![Contributors](https://img.shields.io/badge/contributors-143-brightgreen.svg?style=flat-square)](CONTRIBUTORS.md) 4 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](ADD_YOUR_NAME.md) 5 | 6 | We're building the biggest contributors list on GitHub. Join developers who've added their name to this growing community. 7 | 8 | ## How to add your name 9 | 10 | It takes 15 seconds. 11 | 12 | 1. **Star this repository** ⭐ (helps us reach more contributors!) 13 | 2. **Open this file:** [ADD_YOUR_NAME.md](ADD_YOUR_NAME.md) 14 | 3. **Fill out the form:** Name, Username, Message 15 | 4. **Submit a pull request** 16 | 5. **Done.** Our automation handles the rest. 17 | 18 | ## The rules 19 | 20 | One contribution per person. Use your real GitHub username. Keep your message respectful. Name can be real or alias. 21 | 22 | Want to update your entry? [Create an issue](https://github.com/SashankBhamidi/git-gang/issues/new?labels=help&template=update-entry.md&title=Update+my+entry) and we'll help you out. 23 | 24 | That's it. 25 | 26 | ## Current contributors 27 | 28 | See everyone who's joined: [CONTRIBUTORS.md](CONTRIBUTORS.md) 29 | 30 | ## Need help? 31 | 32 | [Open an issue](https://github.com/SashankBhamidi/git-gang/issues/new/choose) and we'll help you out. 33 | 34 | ## Ready? 35 | 36 | **[Add your name here](ADD_YOUR_NAME.md)** -------------------------------------------------------------------------------- /scripts/check-profanity.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const { checkEntryProfanity } = require('./validation-utils'); 5 | 6 | async function checkEntry(entry) { 7 | const nameMatch = entry.match(/^\[([^\]]+)\]/); 8 | const messageMatch = entry.match(/ - (.+)$/); 9 | 10 | const name = nameMatch ? nameMatch[1] : ''; 11 | const message = messageMatch ? messageMatch[1] : ''; 12 | 13 | const profanityCheck = await checkEntryProfanity(name, message); 14 | 15 | return { 16 | ...profanityCheck, 17 | name, 18 | message 19 | }; 20 | } 21 | 22 | async function main() { 23 | const entry = process.argv[2]; 24 | const username = process.argv[3]; 25 | const prNumber = process.argv[4]; 26 | 27 | if (!entry || !username || !prNumber) { 28 | console.log('Usage: node check-profanity.js "entry" "username" "pr_number"'); 29 | process.exit(1); 30 | } 31 | 32 | const result = await checkEntry(entry); 33 | 34 | if (result.hasProfanity) { 35 | console.log('Profanity detected'); 36 | console.log(`Entry: ${entry}`); 37 | console.log(`Username: ${username}`); 38 | console.log(`PR: #${prNumber}`); 39 | if (process.env.GITHUB_OUTPUT) { 40 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `profanity_detected=true\n`); 41 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `entry=${entry}\n`); 42 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `username=${username}\n`); 43 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `pr_number=${prNumber}\n`); 44 | } 45 | } else { 46 | console.log('No profanity detected'); 47 | if (process.env.GITHUB_OUTPUT) { 48 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `profanity_detected=false\n`); 49 | } 50 | } 51 | } 52 | 53 | main(); 54 | -------------------------------------------------------------------------------- /scripts/sort-contributors.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | 5 | const MAINTAINERS = ['SashankBhamidi', 'github-actions[bot]']; 6 | 7 | function sortContributors() { 8 | try { 9 | const content = fs.readFileSync('CONTRIBUTORS.md', 'utf8'); 10 | const lines = content.split('\n'); 11 | 12 | // Extract contributor entries 13 | const maintainerEntries = []; 14 | const regularEntries = []; 15 | const otherLines = []; 16 | 17 | for (const line of lines) { 18 | // Match both "- [Name]..." and "[Name]..." formats 19 | const entryMatch = line.match(/^-?\s*\[([^\]]+)\]\(https:\/\/github\.com\/([^)]+)\)/); 20 | 21 | if (entryMatch) { 22 | const name = entryMatch[1]; 23 | const username = entryMatch[2]; 24 | 25 | if (MAINTAINERS.includes(username)) { 26 | maintainerEntries.push(line); 27 | } else { 28 | const firstName = name.split(' ')[0].toLowerCase(); 29 | regularEntries.push({ firstName, line }); 30 | } 31 | } else { 32 | otherLines.push(line); 33 | } 34 | } 35 | 36 | // Sort regular entries alphabetically by first name 37 | regularEntries.sort((a, b) => a.firstName.localeCompare(b.firstName)); 38 | 39 | // Calculate total count 40 | const totalCount = maintainerEntries.length + regularEntries.length; 41 | 42 | // Rebuild the file using array for better performance 43 | const outputParts = []; 44 | let addedContent = false; 45 | 46 | for (const line of otherLines) { 47 | if (line.includes('## Our Contributors') && !addedContent) { 48 | outputParts.push(line); 49 | outputParts.push(''); 50 | outputParts.push(`Total contributors: ${totalCount}`); 51 | outputParts.push(''); 52 | 53 | // Add maintainer entries first with consistent list format 54 | for (const entry of maintainerEntries) { 55 | const normalizedEntry = entry.trim().startsWith('-') ? entry : '- ' + entry.trim(); 56 | outputParts.push(normalizedEntry); 57 | } 58 | 59 | // Add sorted regular entries with consistent list format 60 | for (const entry of regularEntries) { 61 | const normalizedEntry = entry.line.trim().startsWith('-') ? entry.line : '- ' + entry.line.trim(); 62 | outputParts.push(normalizedEntry); 63 | } 64 | 65 | addedContent = true; 66 | } else if (!line.startsWith('Total contributors:')) { 67 | outputParts.push(line); 68 | } 69 | } 70 | 71 | fs.writeFileSync('CONTRIBUTORS.md', outputParts.join('\n') + '\n'); 72 | console.log(`Contributors sorted (${totalCount} total: ${maintainerEntries.length} maintainers, ${regularEntries.length} regular)`); 73 | 74 | } catch (error) { 75 | console.error('Failed to sort contributors:', error.message); 76 | process.exit(1); 77 | } 78 | } 79 | 80 | sortContributors(); -------------------------------------------------------------------------------- /scripts/validate-contribution.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Basic validation script without profanity checking 5 | * Uses shared validation-utils module 6 | */ 7 | 8 | const fs = require('fs'); 9 | const { 10 | MAINTAINERS, 11 | ENTRY_REGEX, 12 | setOutput, 13 | setError, 14 | parseSimpleFormat, 15 | validateFormat, 16 | checkExistingContributor, 17 | sanitizeInput 18 | } = require('./validation-utils'); 19 | 20 | function main() { 21 | try { 22 | const content = fs.readFileSync('ADD_YOUR_NAME.md', 'utf8'); 23 | const lines = content.split('\n'); 24 | 25 | // Check if file has required template structure with exact heading match 26 | const firstLine = lines[0].trim(); 27 | if (firstLine !== '# Add Your Name') { 28 | setError('The first line must be exactly "# Add Your Name". Do not modify the template heading.'); 29 | return; 30 | } 31 | 32 | const hasInstructions = content.includes('Want to join the Git Gang?'); 33 | const hasSectionMarker = content.includes('Add your entry below this line'); 34 | 35 | if (!hasInstructions || !hasSectionMarker) { 36 | setError('ADD_YOUR_NAME.md template structure is missing. Please keep the original template and only fill in your details below "Add your entry below this line".'); 37 | return; 38 | } 39 | 40 | // Check if the entry section has the list format markers 41 | const entryContent = content.substring(content.indexOf('Add your entry below this line')); 42 | const hasListFormat = entryContent.includes('- Name:') && entryContent.includes('- Username:') && entryContent.includes('- Message:'); 43 | 44 | if (!hasListFormat) { 45 | setError('Please use the list format: "- Name:", "- Username:", "- Message:" (with dashes). Do not remove the template structure.'); 46 | return; 47 | } 48 | 49 | const sectionIndex = lines.findIndex(line => line.includes('Add your entry below this line')); 50 | if (sectionIndex === -1) { 51 | setError('Could not find the entry section in ADD_YOUR_NAME.md'); 52 | return; 53 | } 54 | 55 | const entrySection = lines.slice(sectionIndex + 1).join('\n').trim(); 56 | const simpleFormatData = parseSimpleFormat(entrySection); 57 | let newEntry, username; 58 | 59 | if (simpleFormatData) { 60 | // Simple format processing 61 | const { name, username: user, message } = simpleFormatData; 62 | username = sanitizeInput(user); 63 | const sanitizedName = sanitizeInput(name); 64 | const sanitizedMessage = message ? sanitizeInput(message) : ''; 65 | const messageText = sanitizedMessage ? ` - ${sanitizedMessage}` : ''; 66 | newEntry = `[${sanitizedName}](https://github.com/${username})${messageText}`; 67 | 68 | // Validate the generated entry format 69 | const validation = validateFormat(newEntry); 70 | if (!validation.valid) { 71 | setError(validation.error); 72 | return; 73 | } 74 | } else { 75 | // Legacy markdown format processing 76 | const trimmedLines = lines.filter(line => line.trim()); 77 | for (let i = trimmedLines.length - 1; i >= 0; i--) { 78 | if (ENTRY_REGEX.test(trimmedLines[i])) { 79 | newEntry = trimmedLines[i]; 80 | break; 81 | } 82 | } 83 | 84 | if (!newEntry) { 85 | setError('No valid entry found. Please add your name using the simple format:\nName: Your Name\nUsername: your-username\nMessage: Optional message'); 86 | return; 87 | } 88 | 89 | const validation = validateFormat(newEntry); 90 | if (!validation.valid) { 91 | setError(validation.error); 92 | return; 93 | } 94 | 95 | username = sanitizeInput(validation.username); 96 | } 97 | 98 | // Check for existing contributor (except maintainers) 99 | if (!MAINTAINERS.includes(username) && checkExistingContributor(username)) { 100 | setError(`@${username} has already been added to the contributors list.`); 101 | return; 102 | } 103 | 104 | setOutput('valid', 'true'); 105 | setOutput('entry', newEntry); 106 | setOutput('username', username); 107 | console.log('SUCCESS: Contribution validated successfully'); 108 | 109 | } catch (error) { 110 | setError(`Validation failed: ${error.message}`); 111 | } 112 | } 113 | 114 | main(); 115 | -------------------------------------------------------------------------------- /.github/workflows/maintain-contribution-issues.yml: -------------------------------------------------------------------------------- 1 | name: Maintain Contribution Issues 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | schedule: 7 | # Run daily at 00:00 UTC to ensure we always have 3 open issues 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | maintain-issues: 16 | # Only run in the main repository, not in forks 17 | if: github.repository == 'SashankBhamidi/git-gang' 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Check contribution issue count 22 | id: check 23 | run: | 24 | set -euo pipefail 25 | 26 | # Count open issues with "contribution" label (with retry) 27 | MAX_RETRIES=3 28 | RETRY_COUNT=0 29 | OPEN_COUNT="" 30 | 31 | while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do 32 | if OPEN_COUNT=$(gh issue list --repo ${{ github.repository }} --label contribution --state open --json number --jq 'length' 2>/dev/null); then 33 | break 34 | fi 35 | RETRY_COUNT=$((RETRY_COUNT + 1)) 36 | if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then 37 | echo "Retry $RETRY_COUNT/$MAX_RETRIES: Waiting 2s..." 38 | sleep 2 39 | fi 40 | done 41 | 42 | if [ -z "$OPEN_COUNT" ]; then 43 | echo "Error: Could not fetch issue count after $MAX_RETRIES attempts" 44 | echo "need_to_create=0" >> $GITHUB_OUTPUT 45 | exit 1 46 | fi 47 | 48 | echo "Found $OPEN_COUNT open contribution issues" 49 | 50 | # Calculate how many we need to create 51 | TARGET=7 52 | NEEDED=$((TARGET - OPEN_COUNT)) 53 | 54 | if [ $NEEDED -gt 0 ]; then 55 | echo "need_to_create=$NEEDED" >> $GITHUB_OUTPUT 56 | echo "Need to create $NEEDED more issue(s) to maintain $TARGET open issues" 57 | else 58 | echo "need_to_create=0" >> $GITHUB_OUTPUT 59 | echo "Already have $TARGET or more open issues" 60 | fi 61 | env: 62 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 63 | 64 | - name: Create contribution issues 65 | if: steps.check.outputs.need_to_create > 0 66 | run: | 67 | set -euo pipefail 68 | 69 | NEEDED=${{ steps.check.outputs.need_to_create }} 70 | CURRENT_MONTH=$(date +%m) 71 | 72 | # Determine labels based on month 73 | if [ "$CURRENT_MONTH" = "10" ]; then 74 | echo "October detected - adding Hacktoberfest label" 75 | LABELS="good first issue,help wanted,contribution,hacktoberfest" 76 | else 77 | LABELS="good first issue,help wanted,contribution" 78 | fi 79 | 80 | # Split labels for gh api 81 | IFS=',' read -ra LABEL_ARRAY <<< "$LABELS" 82 | 83 | # Create issue body file 84 | cat > /tmp/issue-body.txt << 'EOFBODY' 85 | Add your name to the contributors list. 86 | 87 | **Note:** No need to ask for assignment - the automation system will auto-assign you an issue within the PR. Just follow the steps below! 88 | 89 | ## How to contribute 90 | 91 | 1. **Star this repository** (helps us reach more contributors!) 92 | 2. Open [ADD_YOUR_NAME.md](https://github.com/SashankBhamidi/git-gang/blob/master/ADD_YOUR_NAME.md) 93 | 3. Click the edit icon 94 | 4. Fill out: 95 | - Name: Your Name 96 | - Username: your-github-username 97 | - Message: Optional message 98 | 5. Open a pull request 99 | 6. Automation validates and merges automatically 100 | 101 | See [README](https://github.com/SashankBhamidi/git-gang/blob/master/README.md) for details. 102 | EOFBODY 103 | 104 | # Create the needed number of issues 105 | CREATED_COUNT=0 106 | FAILED_COUNT=0 107 | 108 | for i in $(seq 1 $NEEDED); do 109 | echo "Creating contribution issue $i of $NEEDED..." 110 | 111 | # Create issue with retry logic 112 | MAX_RETRIES=3 113 | RETRY_COUNT=0 114 | ISSUE_CREATED=false 115 | 116 | while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do 117 | if gh api repos/${{ github.repository }}/issues -X POST \ 118 | -f title="Add your name to Git Gang" \ 119 | -F body=@/tmp/issue-body.txt \ 120 | -f "labels[]=${LABEL_ARRAY[0]}" \ 121 | -f "labels[]=${LABEL_ARRAY[1]}" \ 122 | -f "labels[]=${LABEL_ARRAY[2]}" \ 123 | $([ "${LABEL_ARRAY[3]}" ] && echo "-f labels[]=${LABEL_ARRAY[3]}" || echo "") 2>/dev/null; then 124 | echo "✓ Created issue $i" 125 | ISSUE_CREATED=true 126 | CREATED_COUNT=$((CREATED_COUNT + 1)) 127 | break 128 | fi 129 | 130 | RETRY_COUNT=$((RETRY_COUNT + 1)) 131 | if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then 132 | echo "Retry $RETRY_COUNT/$MAX_RETRIES: Waiting 2s..." 133 | sleep 2 134 | else 135 | echo "⚠ Failed to create issue $i after $MAX_RETRIES attempts" 136 | FAILED_COUNT=$((FAILED_COUNT + 1)) 137 | fi 138 | done 139 | 140 | # Add 1-second delay between creations to prevent hitting GitHub API rate limits 141 | if [ $i -lt $NEEDED ]; then 142 | sleep 1 143 | fi 144 | done 145 | 146 | echo "✅ Created $CREATED_COUNT/$NEEDED issues" 147 | if [ $FAILED_COUNT -gt 0 ]; then 148 | echo "⚠ Warning: $FAILED_COUNT issue(s) failed to create" 149 | fi 150 | env: 151 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 152 | -------------------------------------------------------------------------------- /scripts/comprehensive-validation.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Comprehensive validation script with profanity checking 5 | * Uses shared validation-utils module 6 | */ 7 | 8 | const fs = require('fs'); 9 | const { 10 | MAINTAINERS, 11 | ENTRY_REGEX, 12 | setOutput, 13 | setError, 14 | parseSimpleFormat, 15 | validateFormat, 16 | checkExistingContributor, 17 | sanitizeInput, 18 | checkEntryProfanity 19 | } = require('./validation-utils'); 20 | 21 | async function validateContribution() { 22 | try { 23 | const content = fs.readFileSync('ADD_YOUR_NAME.md', 'utf8'); 24 | const lines = content.split('\n'); 25 | 26 | // Check if file has required template structure with exact heading match 27 | const firstLine = lines[0].trim(); 28 | if (firstLine !== '# Add Your Name') { 29 | setError('The first line must be exactly "# Add Your Name". Do not modify the template heading.'); 30 | return false; 31 | } 32 | 33 | const hasInstructions = content.includes('Want to join the Git Gang?'); 34 | const hasSectionMarker = content.includes('Add your entry below this line'); 35 | 36 | if (!hasInstructions || !hasSectionMarker) { 37 | setError('ADD_YOUR_NAME.md template structure is missing. Please keep the original template and only fill in your details below "Add your entry below this line".'); 38 | return false; 39 | } 40 | 41 | // Check if the entry section has the list format markers 42 | const entryContent = content.substring(content.indexOf('Add your entry below this line')); 43 | const hasListFormat = entryContent.includes('- Name:') && entryContent.includes('- Username:') && entryContent.includes('- Message:'); 44 | 45 | if (!hasListFormat) { 46 | setError('Please use the list format: "- Name:", "- Username:", "- Message:" (with dashes). Do not remove the template structure.'); 47 | return false; 48 | } 49 | 50 | const sectionIndex = lines.findIndex(line => line.includes('Add your entry below this line')); 51 | if (sectionIndex === -1) { 52 | setError('Could not find the entry section in ADD_YOUR_NAME.md'); 53 | return false; 54 | } 55 | 56 | const entrySection = lines.slice(sectionIndex + 1).join('\n').trim(); 57 | const simpleFormatData = parseSimpleFormat(entrySection); 58 | let newEntry, username, name, message; 59 | 60 | if (simpleFormatData) { 61 | // Simple format processing 62 | const { name: parsedName, username: parsedUsername, message: parsedMessage } = simpleFormatData; 63 | username = sanitizeInput(parsedUsername); 64 | name = sanitizeInput(parsedName); 65 | message = parsedMessage ? sanitizeInput(parsedMessage) : ''; 66 | const messageText = message ? ` - ${message}` : ''; 67 | newEntry = `[${name}](https://github.com/${username})${messageText}`; 68 | 69 | // Validate the generated entry format 70 | const validation = validateFormat(newEntry); 71 | if (!validation.valid) { 72 | setError(validation.error); 73 | return false; 74 | } 75 | 76 | // Use the converted entry if available (simple format was converted to markdown) 77 | newEntry = validation.entry || newEntry; 78 | username = sanitizeInput(validation.username); 79 | name = sanitizeInput(validation.name); 80 | message = validation.message ? sanitizeInput(validation.message) : ''; 81 | } else { 82 | // Legacy markdown format processing 83 | const trimmedLines = lines.filter(line => line.trim()); 84 | for (let i = trimmedLines.length - 1; i >= 0; i--) { 85 | if (ENTRY_REGEX.test(trimmedLines[i])) { 86 | newEntry = trimmedLines[i]; 87 | break; 88 | } 89 | } 90 | 91 | if (!newEntry) { 92 | setError('Your entry is not in the correct format. Please use: Name: Your Name, Username: your-username, Message: Optional message (or the markdown link format)'); 93 | return false; 94 | } 95 | 96 | const validation = validateFormat(newEntry); 97 | if (!validation.valid) { 98 | setError(validation.error); 99 | return false; 100 | } 101 | 102 | // Use the converted entry if available 103 | newEntry = validation.entry || newEntry; 104 | username = sanitizeInput(validation.username); 105 | name = sanitizeInput(validation.name); 106 | message = validation.message ? sanitizeInput(validation.message) : ''; 107 | } 108 | 109 | // Check for existing contributor (except maintainers) 110 | if (!MAINTAINERS.includes(username) && checkExistingContributor(username)) { 111 | setError(`@${username} has already been added to the contributors list.`); 112 | return false; 113 | } 114 | 115 | // Check for profanity 116 | const profanityCheck = await checkEntryProfanity(name, message); 117 | 118 | setOutput('valid', 'true'); 119 | setOutput('entry', newEntry); 120 | setOutput('username', username); 121 | setOutput('name', name); 122 | setOutput('message', message || ''); 123 | setOutput('profanity_detected', profanityCheck.hasProfanity ? 'true' : 'false'); 124 | setOutput('profanity_in_name', profanityCheck.profanityInName ? 'true' : 'false'); 125 | setOutput('profanity_in_message', profanityCheck.profanityInMessage ? 'true' : 'false'); 126 | 127 | if (profanityCheck.hasProfanity) { 128 | console.log('WARNING: Potential profanity detected in contribution'); 129 | if (profanityCheck.profanityInName) { 130 | console.log(' - Detected in name field'); 131 | } 132 | if (profanityCheck.profanityInMessage) { 133 | console.log(' - Detected in message field'); 134 | } 135 | } else { 136 | console.log('SUCCESS: Contribution validated successfully'); 137 | } 138 | 139 | return true; 140 | 141 | } catch (error) { 142 | setError(`Validation failed: ${error.message}`); 143 | return false; 144 | } 145 | } 146 | 147 | // Run validation 148 | validateContribution().then(success => { 149 | process.exit(success ? 0 : 1); 150 | }).catch(error => { 151 | console.error('Unexpected error:', error); 152 | process.exit(1); 153 | }); 154 | -------------------------------------------------------------------------------- /scripts/validation-utils.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Shared validation utilities for Git Gang contribution processing 5 | */ 6 | 7 | const fs = require('fs'); 8 | 9 | const MAINTAINERS = ['SashankBhamidi', 'github-actions[bot]']; 10 | const ENTRY_REGEX = /^\[([^\]]+)\]\(https:\/\/github\.com\/([^)]+)\)(?: - (.+))?$/; 11 | 12 | function setOutput(name, value) { 13 | if (process.env.GITHUB_OUTPUT) { 14 | fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); 15 | } else { 16 | console.log(`${name}=${value}`); 17 | } 18 | } 19 | 20 | function setError(message) { 21 | setOutput('valid', 'false'); 22 | setOutput('error_message', message); 23 | console.error(`ERROR: ${message}`); 24 | } 25 | 26 | function capitalizeWords(str) { 27 | // Preserve special cases like O'Brien, McDonald, Mary-Jane 28 | return str.split(' ') 29 | .map(word => { 30 | // Skip empty words 31 | if (!word) return word; 32 | 33 | // Handle hyphenated names (Mary-Jane) 34 | if (word.includes('-')) { 35 | return word.split('-') 36 | .map(part => part && part.length > 0 ? part.charAt(0).toUpperCase() + part.slice(1).toLowerCase() : part) 37 | .join('-'); 38 | } 39 | // Handle names with apostrophes (O'Brien, D'Angelo) 40 | if (word.includes("'")) { 41 | return word.split("'") 42 | .map(part => part && part.length > 0 ? part.charAt(0).toUpperCase() + part.slice(1).toLowerCase() : part) 43 | .join("'"); 44 | } 45 | // Handle names starting with Mc/Mac (McDonald, MacLeod) 46 | if (word.toLowerCase().startsWith('mc') && word.length > 2) { 47 | return 'Mc' + word.charAt(2).toUpperCase() + word.slice(3).toLowerCase(); 48 | } 49 | if (word.toLowerCase().startsWith('mac') && word.length > 3) { 50 | return 'Mac' + word.charAt(3).toUpperCase() + word.slice(4).toLowerCase(); 51 | } 52 | // Standard capitalization 53 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 54 | }) 55 | .join(' '); 56 | } 57 | 58 | function parseSimpleFormat(text) { 59 | // Support both list format (- Name:) and plain format (Name:) 60 | const nameMatch = text.match(/(?:-\s*)?Name:\s*(.+?)(?:\r?\n|$)/i); 61 | const usernameMatch = text.match(/(?:-\s*)?Username:\s*(.+?)(?:\r?\n|$)/i); 62 | const messageMatch = text.match(/(?:-\s*)?Message:\s*(.*?)(?:\r?\n|$)/i); 63 | 64 | if (!nameMatch || !usernameMatch) { 65 | return null; 66 | } 67 | 68 | // Extract username from various formats 69 | let username = usernameMatch[1].trim(); 70 | // Strip @ prefix 71 | username = username.replace(/^@/, ''); 72 | // Strip GitHub URL (https://github.com/username or github.com/username) 73 | username = username.replace(/^(?:https?:\/\/)?(?:www\.)?github\.com\//, ''); 74 | // Remove trailing slashes 75 | username = username.replace(/\/$/, ''); 76 | 77 | return { 78 | name: capitalizeWords(nameMatch[1].trim()), 79 | username: username, 80 | message: messageMatch ? messageMatch[1].trim() : '' 81 | }; 82 | } 83 | 84 | function validateFormat(line) { 85 | // Try to parse as simple format first (Name:/Username:/Message:) 86 | const simpleFormatData = parseSimpleFormat(line); 87 | if (simpleFormatData) { 88 | const { name, username, message } = simpleFormatData; 89 | 90 | // Validate username format 91 | if (!/^[a-z\d](?:[a-z\d]|[-_](?=[a-z\d])){0,38}$/i.test(username)) { 92 | return { valid: false, error: 'Invalid GitHub username format' }; 93 | } 94 | 95 | // Convert to markdown link format 96 | const markdownEntry = `[${name}](https://github.com/${username})${message ? ' - ' + message : ''}`; 97 | return { valid: true, name, username, message, entry: markdownEntry }; 98 | } 99 | 100 | // Fall back to markdown link format validation 101 | const match = line.match(ENTRY_REGEX); 102 | if (!match) { 103 | return { valid: false, error: 'Format is incorrect. Use format: Name: Your Name, Username: yourusername, Message: Your message' }; 104 | } 105 | 106 | const [, name, username, message] = match; 107 | 108 | // Additional validation checks 109 | if (!name || name.trim().length === 0) { 110 | return { valid: false, error: 'Name cannot be empty' }; 111 | } 112 | 113 | if (!username || username.trim().length === 0) { 114 | return { valid: false, error: 'Username cannot be empty' }; 115 | } 116 | 117 | // Basic username validation (GitHub username rules: alphanumeric, hyphens, underscores, max 39 chars) 118 | if (!/^[a-z\d](?:[a-z\d]|[-_](?=[a-z\d])){0,38}$/i.test(username)) { 119 | return { valid: false, error: 'Invalid GitHub username format' }; 120 | } 121 | 122 | return { valid: true, name, username, message, entry: line }; 123 | } 124 | 125 | function checkExistingContributor(username) { 126 | try { 127 | let contributors; 128 | 129 | // In GitHub Actions, check against the latest master branch to avoid race conditions 130 | // where multiple PRs are opened/merged in quick succession 131 | if (process.env.GITHUB_ACTIONS === 'true') { 132 | const { execSync } = require('child_process'); 133 | try { 134 | // Read CONTRIBUTORS.md from origin/master (latest merged state) 135 | contributors = execSync('git show origin/master:CONTRIBUTORS.md', { 136 | encoding: 'utf8', 137 | stdio: ['pipe', 'pipe', 'pipe'] 138 | }); 139 | } catch (gitError) { 140 | console.warn('Warning: Could not read CONTRIBUTORS.md from origin/master, falling back to local copy'); 141 | contributors = fs.readFileSync('CONTRIBUTORS.md', 'utf8'); 142 | } 143 | } else { 144 | // Local development: read from filesystem 145 | contributors = fs.readFileSync('CONTRIBUTORS.md', 'utf8'); 146 | } 147 | 148 | const regex = new RegExp(`https://github\\.com/${username}\\)`, 'i'); 149 | return regex.test(contributors); 150 | } catch (error) { 151 | console.warn(`Warning: Could not read CONTRIBUTORS.md: ${error.message}`); 152 | return false; 153 | } 154 | } 155 | 156 | function sanitizeInput(input) { 157 | // Remove potential shell injection characters and trim 158 | return input.replace(/[;&|`$(){}[\]<>]/g, '').trim(); 159 | } 160 | 161 | async function checkProfanityAPI(text, retryCount = 0) { 162 | const MAX_RETRIES = 3; 163 | const TIMEOUT_MS = 5000; 164 | const RETRY_DELAYS = [1000, 2000, 3000]; 165 | 166 | try { 167 | const controller = new AbortController(); 168 | const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS); 169 | 170 | const response = await fetch( 171 | `https://www.purgomalum.com/service/containsprofanity?text=${encodeURIComponent(text)}`, 172 | { signal: controller.signal } 173 | ); 174 | 175 | clearTimeout(timeoutId); 176 | const result = await response.text(); 177 | return result.toLowerCase() === 'true'; 178 | } catch (error) { 179 | console.log(`Profanity API check failed for "${text}" (attempt ${retryCount + 1}/${MAX_RETRIES}): ${error.message}`); 180 | 181 | if (retryCount < MAX_RETRIES - 1) { 182 | const delay = RETRY_DELAYS[retryCount]; 183 | console.log(`Retrying in ${delay}ms...`); 184 | await new Promise(resolve => setTimeout(resolve, delay)); 185 | return checkProfanityAPI(text, retryCount + 1); 186 | } 187 | 188 | console.warn(`Profanity API unavailable after ${MAX_RETRIES} attempts. Flagging for manual review.`); 189 | return true; 190 | } 191 | } 192 | 193 | async function checkEntryProfanity(name, message) { 194 | // Run both checks in parallel for better performance 195 | const checks = []; 196 | let nameIndex = null; 197 | let messageIndex = null; 198 | 199 | if (name) { 200 | nameIndex = checks.length; 201 | checks.push(checkProfanityAPI(name)); 202 | } 203 | if (message) { 204 | messageIndex = checks.length; 205 | checks.push(checkProfanityAPI(message)); 206 | } 207 | 208 | if (checks.length === 0) { 209 | return { hasProfanity: false, profanityInName: false, profanityInMessage: false }; 210 | } 211 | 212 | const results = await Promise.all(checks); 213 | const profanityInName = nameIndex !== null ? results[nameIndex] : false; 214 | const profanityInMessage = messageIndex !== null ? results[messageIndex] : false; 215 | 216 | return { 217 | hasProfanity: profanityInName || profanityInMessage, 218 | profanityInName, 219 | profanityInMessage 220 | }; 221 | } 222 | 223 | module.exports = { 224 | MAINTAINERS, 225 | ENTRY_REGEX, 226 | setOutput, 227 | setError, 228 | capitalizeWords, 229 | parseSimpleFormat, 230 | validateFormat, 231 | checkExistingContributor, 232 | sanitizeInput, 233 | checkProfanityAPI, 234 | checkEntryProfanity 235 | }; 236 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | Welcome to the Git Gang! These amazing developers have joined our community. 4 | 5 | ## Our Contributors 6 | 7 | Total contributors: 143 8 | 9 | - [Sashank Bhamidi](https://github.com/SashankBhamidi) - Creator of Git Gang - building GitHub's biggest contributors list! 10 | - [Aakash Raj](https://github.com/aakash-sriv) - i promise to contribute better and more meaningful commits in OSS community. 11 | - [Aayush Bhadbhade](https://github.com/AayushJB03) - Excited to contribute to this amazing open-source community! Happy to be part of the Git Gang and support collaborative development. 12 | - [Abc](https://github.com/akshith2855) - abcdef 13 | - [Abhinav Rajput](https://github.com/abhinavxdd) - Hmm...Well, huh! 14 | - [Aditya](https://github.com/aditya-yadav1176) - heyy 15 | - [Adripo](https://github.com/adripo) - Keep up the good work! 16 | - [Advay Sinha](https://github.com/advay-sinha) - pr req 17 | - [Akshat](https://github.com/AkshatKumar10) - Hey!excited to be part of git gang 18 | - [Altair](https://github.com/Altaeir-13) - Hello World! How are y'all doing? 19 | - [Aman Singhal](https://github.com/trock3338) - I am Full-stack developer and i love contributing to open source projects, this one is for hacktoberFest. 20 | - [Amano](https://github.com/fzlfade) - Keep happy! 21 | - [Ameer Sampang Rai](https://github.com/raijin-asr) - Happy contribution to Hacktoberfest 2025! 22 | - [Ameya](https://github.com/Raptor0G) - Let's cook! 23 | - [Amrita Mishra](https://github.com/amritamishra01) - Excited to join the Git Gang and contribute! 🌸 24 | - [Amritanshu Kumar](https://github.com/amritanshu2005) - Add Amritanshu Kumar to contributors list 25 | - [Anas Khan](https://github.com/Anas2604-web) - Excited to join the Git Gang and contribute this Hacktoberfest! 🚀 26 | - [Anirudh Panigrahi](https://github.com/Anirudh-020505) - done 27 | - [Anish Panda](https://github.com/Anish005) - LET's build 28 | - [Ankur](https://github.com/Ankur071) - joining the community! 29 | - [Anmol Sah](https://github.com/anmolsah) 30 | - [Ansh](https://github.com/ansh3108) - sup?! 31 | - [Ansh Meshram](https://github.com/AnshMeshram) - An engineer in the making, driven by curiosity and creativity — I craft digital solutions that connect technology with real-world impact. 32 | - [Ansh Kumar](https://github.com/Akrodriguez) - Excited to join the git-gang for hacktober 2025!!! : 33 | - [Anurag Ghutke](https://github.com/AnuragGhutke) - Excited to contribute ! 34 | - [Apurva](https://github.com/apurvavats) - Excited to join Git Gang! 35 | - [Arav Thakkar](https://github.com/Aaravthk) - Hey everyone, I am looking forward to contribute in open source projects 36 | - [Archisman Nath Choudhury](https://github.com/Archisman-NC) - No idea 37 | - [Arpit](https://github.com/M1CTIAN) - Probably thinking about food 38 | - [Aryan Deshmukh](https://github.com/aryan118335) - Excited to start contributing! 39 | - [Asha Saini](https://github.com/AshaSaini-033) - Thank You 40 | - [Ashvin Tiwari](https://github.com/ashvin2005) - love you all 41 | - [Asish Pradhan](https://github.com/asishpradhan01) - This is my first contribution 42 | - [Astel Tom](https://github.com/astel-code) 43 | - [Ayushi](https://github.com/Ayushi20052006) - I will be glad to join this git gang ☺️ 44 | - [Bhoomi](https://github.com/Bhoomi070) - Happy Hacktoberfest! Excited to join the community! 🎉 45 | - [Bhumi N Deshpande](https://github.com/bhumindeshpande8-spec) - HacktoberFest 2025! Excited to join the community : 46 | - [Bhuswarna Kashyap](https://github.com/RyanCarlisle) - You either die a hero, or live long enough to become the legacy system. 47 | - [Bhuvanb](https://github.com/BhuvanB404) - Have a good day ! 48 | - [Broken Jsx](https://github.com/SuvanshTembe) - Added My name to Git Gang 49 | - [Coder-Pro](https://github.com/Coder-pro1) - First hacktoberfest done 50 | - [Comet](https://github.com/unknownIoT) - Contributing via automation 51 | - [Daksh Aggarwal](https://github.com/Daksh-Aggarwal) - Hey everyone! 52 | - [Daniel Chinasa Okoro](https://github.com/danielchinasa) - Currently contributing to open-source projects to support innovation and advancement in the tech space 53 | - [Dhairya Goel](https://github.com/dhairyagoel-git) - Hello coders !! 54 | - [Dipak Kumar Agrahari](https://github.com/sea-deep) - yoo I'm new to programming :3 55 | - [Divyansh Mishra](https://github.com/mishraa-G) - Excited to tackle some good issues 56 | - [Emin Naggayev](https://github.com/Emin-062) - So happy to join! 57 | - [Enovice23](https://github.com/raj-programs) - Excited To join the community 58 | - [Fairda Sabrin](https://github.com/FaridaSabrin) - I’m genuinely grateful to be part of such an innovative and collaborative tech community. Every line of code we write, every bug we fix, and every idea we share brings us one step closer to building something impactful. 59 | - [Falak Mishra](https://github.com/Falak7531) - get well soon 60 | - [Gajjela Vasudev](https://github.com/GajjelaVasudev) - Excited to Contribute! 61 | - [Gary Miller](https://github.com/metacoder87) - Happy Hacktober! 62 | - [Hans](https://github.com/hans-r7) - hacktoberfest is an awesome way of contributing to open source PRs! 63 | - [Heisenberg](https://github.com/Srinu346) - Enjoying Hacktober Fest 64 | - [Hrithik](https://github.com/hrithiksawhney) - Excited to contribute to this amazing open-source community! Happy to be part of the Git Gang and support collaborative development. 65 | - [Insha](https://github.com/Insha-7) - I am a contributor!! 66 | - [Jeet Singh](https://github.com/jeetsingh008) - Happy to be part of contributors list of Git-Gang. 67 | - [Jesus Peralta](https://github.com/Chucho-Kun) - Im a Fullstack Developer from Mexico City 68 | - [Kaleemullah Ahsan](https://github.com/axndev) - Excited to contribute and be part of this community! 69 | - [Kamini Prajapati](https://github.com/Kamini8707) - I want to contribute to hacktoberfest and raising a PR please merge it 70 | - [Kanishka](https://github.com/kanishka1804) - happy to contribute 71 | - [Likhitha Kathireddy](https://github.com/Likhithakathireddy) - Excited to join and collaborate with the Gang! 72 | - [Loid Forger](https://github.com/cheese-cakee) - trying out hacktoberfest 73 | - [Lonelydev](https://github.com/some-boi) - just a cool guy moving through 74 | - [Luuk](https://github.com/Devluuk123) - Git-ging is a awesome idea of Sashank where I want to be part of by contributing in a simple way. 75 | - [Lyla Byun](https://github.com/LylaB) - Hello world! 76 | - [Madhumita Mandal](https://github.com/madhumitaaa) - Keep learning, keep building, and keep growing 77 | - [Madhur Dodake](https://github.com/Madhur-Dodake) - Thanks for this 78 | - [Magnet](https://github.com/neerajgoud1) - Hello ! 79 | - [Manideep](https://github.com/manideepyelugam) - I'm happy to be in the gang 80 | - [Matei Budiu](https://github.com/aehmttw) - Hello world!!! 81 | - [Mayur](https://github.com/MayurK-cmd) - Blah blah 82 | - [Memassine](https://github.com/ME-Massine) - Excited to join! 83 | - [Moksh Mutreja](https://github.com/Moksh-Mutreja) - Hacktoberfest is an awesome way of contributing to open source PRs! 84 | - [Monotosh Kumar Das Chandan](https://github.com/monochandan) - Want to contribute on - AI, ML, NLP 85 | - [Muhammad Umar Aziz](https://github.com/umar1110) - Excited to contribute to this amazing open-source community! Happy to be part of the Git Gang and support collaborative development. See you : 86 | - [Muhammad Ibnu Fauzi](https://github.com/ifauzeee) - Hi There 87 | - [Nakul](https://github.com/n4ku7) - lol 88 | - [Naveen](https://github.com/naveenkumar29052006) - LFG 89 | - [Nayandeep Goswami](https://github.com/NayandG07) - Excited to join git-gang! 🚀 90 | - [No Tea Biscuit](https://github.com/Aadrxh) - jalenda 91 | - [Nur Hasin Ahammad](https://github.com/nur-hasin) - Glad to join this community through Hacktoberfest! 🚀 92 | - [Om](https://github.com/Om7035) - Excited to join the Git Gang community! 93 | - [Omkar Hadole](https://github.com/omkar-hadole) - NA 94 | - [Parth Parmar](https://github.com/ParthParmar88) - Excited to contribute to this amazing open-source community! Happy to be part of the Git Gang and support collaborative development. 95 | - [Piyush Mishra](https://github.com/PiyushMishra009) - Grateful to Contribute to git gang community and support open-source growth during Hacktoberfest 2025. 96 | - [Pranay Gadh](https://github.com/Pranay22077) - Let the powerful play go on and you may contribute a verse... 97 | - [Preet Chauhan](https://github.com/PREETCHAUHAN2005) - I am a student passionate to learn about Artificial General Intelligence and I want to join the Community of AGI Learners. Feel free to connect with me if you are also passionate about AGI. 98 | - [Prithviraj](https://github.com/bundela05) - letzz go! 99 | - [Priyansh Jain](https://github.com/Priyanshjain10) - Excited to join the Git Gang! 100 | - [Rafia](https://github.com/rafia-codes) - Nice Idea 101 | - [Raja Rathour](https://github.com/Raja-89) - This is my contribution for Hactober 2025 102 | - [Rajucreate](https://github.com/rajucreate) - Hello, Everyone 103 | - [Ramla Eman](https://github.com/Ramla-Eman) - Happy to join as a Full Stack Developer 104 | - [Raunak Mishra](https://github.com/raunak-mishraa) - Excited to be part of the Git Gang! 🚀 105 | - [Raunak Mishra](https://github.com/raunak-devs) - Just another dev pushing code and pulling dreams! 💻🔥 106 | - [Rishabh Kaushik](https://github.com/Rishu222006) - Just happy to be here. 107 | - [Robert Palmer](https://github.com/RJPalmer) - Money can be exchanged for goods and services. 108 | - [Rodrigo Muñoz](https://github.com/D3PA) - Happy to join the Git Gang 😎 109 | - [Roopa](https://github.com/Rupa-Rd) - This is my last PR for Hacktoberfest 2025. 110 | - [Samarth Shendre](https://github.com/i-m-samarth-cs) - Excited to join and collaborate with the Gang! 111 | - [San](https://github.com/Cyberpunk-San) - Yo!Sup , just came here to say hi to the git gang. 112 | - [Saprem Khot](https://github.com/KhotSaprem) - just a fellow message 113 | - [Saumya Bisht](https://github.com/SaumyaBish-t) - Code for good 114 | - [Shankypedia](https://github.com/shankypedia) - What's good? 115 | - [Sharan Garikapati](https://github.com/sairamsharan) - Glad to be here! 116 | - [Sharvandeep](https://github.com/sharvandeep) - HEY 117 | - [Shatakshi](https://github.com/shatakshi220805) - this is my second PR 118 | - [Shohan](https://github.com/Shohan20lac) - Hi Shashank, love what you're trying to do here! Apes together strong. 🦍🦍🦍. I look forward to contributing and making the Open Source world a better place. Cheers! 119 | - [Shrasti Jaiswal](https://github.com/SrishJ23) - Excited to join Git Gang! 🚀 120 | - [Shriya Nayak](https://github.com/shriyanyk) 121 | - [Shubham Yadav](https://github.com/shubhamyadav2809) - joining the Git Gang. 122 | - [Siddhant Shekhar](https://github.com/sshekhar563) - Happy to contribute 123 | - [Sitesurfer](https://github.com/Sakshi7654) - Hello Coders! 124 | - [Sneha Chourey](https://github.com/sneha-chourey) - happy to contribute here! 125 | - [Sneha Hegde](https://github.com/Sneha0562) - Hi 126 | - [Soham More](https://github.com/SohamProg) - Hey! Beginner to open-source contribution. Begins with hacktoberfest! 127 | - [Sudha Bhamidi](https://github.com/SudhaBhamidi) - Proud supporter of Git Gang! 128 | - [Sujal Thakur](https://github.com/sujal-thakur01) - I am a driven tech learner on a quest for coding excellence. Fuelled by passion, curiosity, and a thirst for growth. 129 | - [Sukarth](https://github.com/Sukarth) - Happy Hacktoberfest from Finland! 🎃🇫🇮 130 | - [Sunshine](https://github.com/R-2400100058) - Hey! looking forward to working on this w the GitGang 131 | - [Suraj S](https://github.com/suarej) - Starting with the open source journey 132 | - [Suyash Sahu](https://github.com/7uyash) - rtyuio 133 | - [Swapnith Kondapalli](https://github.com/swapnith12) - I'm looking to contribute for javascript NodeJS,ReactJS and NextJS issues 134 | - [Talasu Deepak Kumar Patro](https://github.com/Talasudeepk) - hi 135 | - [Tanishq](https://github.com/TanishqDNEC) - Hey!excited to be part of git gang 136 | - [Tushar](https://github.com/tusharshah21) - Full Stack Developer Crafting Unique Digital Solutions 137 | - [Ugur Ozcan](https://github.com/uozcan12) - Hello Git Gang contributors! 138 | - [Vansh Gupta](https://github.com/vanshgupta11) - Hi fello members , it would be great to work with you all. 139 | - [Vansh Nandwani](https://github.com/vansh2408) - Let's innovate together! 140 | - [Vanshika Shah](https://github.com/Vanshika9S) - Keep learning! 141 | - [Vanshika](https://github.com/vanshikap21) - This is my First PR 142 | - [Vara Rahul Rajana](https://github.com/rajanarahul93) - Excited to join the Git Gang community! 143 | - [Vectorsigmaomega](https://github.com/VectorSigmaOmega) - Thanks! 144 | - [Vedant Tapkir](https://github.com/Octaflick) - My 2nd PR 145 | - [Vihaan Zutshi](https://github.com/vihaanified) - Can't wait to see this grow! 146 | - [Vik Williamson](https://github.com/vikwilliamson) - Bodybuilder. Drummer. Dev. - Find me everywhere @thedreadeddragon 🐉 #GitGang4L 147 | - [Vipul Kumar](https://github.com/vipul264og) - excited to join 148 | - [Vishal M](https://github.com/vishalm342) - Excited to join the Git-Gang community! 149 | - [Vivek Upadhyay](https://github.com/vivek33up) - Happy Coding! 150 | - [Yash Akarsh](https://github.com/YashAkarsh) 151 | - [Yashpss01](https://github.com/yashpss01) - Hello Contributers !! 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /.github/workflows/post-merge-process.yml: -------------------------------------------------------------------------------- 1 | name: Post-Merge Processing 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: ["ADD_YOUR_NAME.md"] 7 | 8 | concurrency: 9 | group: post-merge-processing-${{ github.sha }} 10 | cancel-in-progress: false 11 | 12 | permissions: 13 | contents: write 14 | issues: write 15 | 16 | jobs: 17 | process-merged-contribution: 18 | # Only run in the main repository, not in forks 19 | if: github.repository == 'SashankBhamidi/git-gang' 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | issues: write 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 28 | with: 29 | fetch-depth: 0 30 | token: ${{ secrets.PAT_TOKEN || github.token }} 31 | 32 | - name: Setup Node.js 33 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 34 | with: 35 | node-version: "18" 36 | 37 | - name: Setup Git configuration 38 | run: | 39 | git config --global user.name "Sashank Bhamidi" 40 | git config --global user.email "hello@sashank.wiki" 41 | 42 | - name: Check if ADD_YOUR_NAME.md has new content 43 | id: check-content 44 | run: | 45 | set -euo pipefail 46 | 47 | if [ ! -f "ADD_YOUR_NAME.md" ]; then 48 | echo "Error: ADD_YOUR_NAME.md not found" 49 | echo "has_new_entry=false" >> $GITHUB_OUTPUT 50 | exit 0 51 | fi 52 | 53 | content=$(cat ADD_YOUR_NAME.md) 54 | # Use printf %s to safely handle commit messages with backticks or special chars 55 | commit_message=$(printf '%s' '${{ github.event.head_commit.message }}') 56 | 57 | # Extract PR number from commit message (format: "Title (#123)") 58 | if echo "$commit_message" | grep -qE '\(#[0-9]+\)'; then 59 | pr_number=$(echo "$commit_message" | grep -oE '#[0-9]+' | head -n 1 | tr -d '#') 60 | echo "Found PR number: $pr_number" 61 | 62 | # Get PR author (may fail if PR is closed/merged and permissions are limited) 63 | if pr_author=$(gh pr view $pr_number --json author --jq '.author.login' 2>/dev/null); then 64 | echo "PR author: $pr_author" 65 | 66 | # Skip processing if PR author is SashankBhamidi (maintainer's custom contributions) 67 | if [ "$pr_author" = "SashankBhamidi" ]; then 68 | echo "has_new_entry=false" >> $GITHUB_OUTPUT 69 | echo "Skipping processing for maintainer's custom contribution" 70 | exit 0 71 | fi 72 | else 73 | echo "Could not determine PR author (PR may be closed), proceeding with normal checks" 74 | fi 75 | else 76 | echo "No PR number found in commit message, proceeding with normal checks" 77 | fi 78 | 79 | # Check for simple format (Name:/Username:/Message:) or markdown link format 80 | # Extract the section after "Add your entry below this line" 81 | entry_section=$(echo "$content" | sed -n '/Add your entry below this line/,$p') 82 | 83 | # Check if there's actual content (not just empty template) 84 | echo "DEBUG: Checking entry section:" 85 | echo "$entry_section" 86 | echo "---" 87 | 88 | # Check for format with dashes (preferred) 89 | # Use [^[:space:]] to ensure there's actual non-whitespace content after the colon 90 | if echo "$entry_section" | grep -qE -- '- Name:[[:space:]]*[^[:space:]]+' && \ 91 | echo "$entry_section" | grep -qE -- '- Username:[[:space:]]*[^[:space:]]+' && \ 92 | echo "$entry_section" | grep -qE -- '- Message:'; then 93 | echo "has_new_entry=true" >> $GITHUB_OUTPUT 94 | echo "✓ New contribution detected for processing (simple format with dashes)" 95 | # Also accept format without dashes (fallback for users who remove them) 96 | elif echo "$entry_section" | grep -qE -- '^[[:space:]]*Name:[[:space:]]*[^[:space:]]+' && \ 97 | echo "$entry_section" | grep -qE -- '^[[:space:]]*Username:[[:space:]]*[^[:space:]]+' && \ 98 | echo "$entry_section" | grep -qE -- '^[[:space:]]*Message:'; then 99 | echo "has_new_entry=true" >> $GITHUB_OUTPUT 100 | echo "✓ New contribution detected for processing (simple format without dashes)" 101 | elif echo "$entry_section" | grep -qE -- '\[.+\]\(https://github.com/[a-zA-Z0-9_-]+\)'; then 102 | echo "has_new_entry=true" >> $GITHUB_OUTPUT 103 | echo "✓ New contribution detected for processing (markdown format)" 104 | else 105 | echo "has_new_entry=false" >> $GITHUB_OUTPUT 106 | echo "ℹ No new contribution to process (template is empty)" 107 | fi 108 | env: 109 | GH_TOKEN: ${{ github.token }} 110 | 111 | - name: Process merged contribution 112 | if: steps.check-content.outputs.has_new_entry == 'true' 113 | id: process 114 | run: | 115 | echo "Processing contribution..." 116 | node scripts/comprehensive-validation.js 117 | 118 | - name: Apply file changes 119 | if: steps.check-content.outputs.has_new_entry == 'true' && steps.process.outputs.valid == 'true' 120 | id: apply-changes 121 | run: | 122 | entry="${{ steps.process.outputs.entry }}" 123 | username="${{ steps.process.outputs.username }}" 124 | 125 | echo "Processing entry: $entry" 126 | echo "Username: $username" 127 | 128 | # Create backup of current state for rollback 129 | git rev-parse HEAD > .git-backup-commit 130 | cp CONTRIBUTORS.md CONTRIBUTORS.md.backup || echo "No CONTRIBUTORS.md to backup" 131 | cp README.md README.md.backup || echo "No README.md to backup" 132 | cp ADD_YOUR_NAME.md ADD_YOUR_NAME.md.backup || echo "No ADD_YOUR_NAME.md to backup" 133 | 134 | # Process the contribution 135 | if ! node scripts/process-contribution.js "$entry"; then 136 | echo "Failed to process contribution" 137 | exit 1 138 | fi 139 | 140 | if ! node scripts/sort-contributors.js; then 141 | echo "Failed to sort contributors" 142 | exit 1 143 | fi 144 | 145 | if ! node scripts/update-badge.js; then 146 | echo "Failed to update badge" 147 | exit 1 148 | fi 149 | 150 | if ! node scripts/reset-staging.js; then 151 | echo "Failed to reset staging" 152 | exit 1 153 | fi 154 | 155 | # Verify files were modified correctly 156 | if ! git diff --quiet HEAD CONTRIBUTORS.md; then 157 | echo "CONTRIBUTORS.md was successfully updated" 158 | else 159 | echo "ERROR: CONTRIBUTORS.md was not updated" 160 | exit 1 161 | fi 162 | 163 | # Commit all changes 164 | git add CONTRIBUTORS.md README.md ADD_YOUR_NAME.md 165 | if ! git commit -m "Process contribution from @$username"; then 166 | echo "Failed to commit changes" 167 | exit 1 168 | fi 169 | 170 | # Configure git with token for authenticated push 171 | # Try PAT_TOKEN first (can bypass branch protection), fall back to github.token 172 | GIT_TOKEN="${{ secrets.PAT_TOKEN || github.token }}" 173 | git remote set-url origin https://x-access-token:${GIT_TOKEN}@github.com/${{ github.repository }}.git 174 | 175 | # Pull latest changes to avoid race conditions with concurrent post-merge workflows 176 | # Use rebase to apply our commit on top of any new changes 177 | echo "Pulling latest changes from master..." 178 | if ! git pull --rebase origin master; then 179 | echo "WARNING: Rebase conflicts detected, attempting merge strategy..." 180 | git rebase --abort 181 | if ! git pull --no-rebase origin master; then 182 | echo "ERROR: Failed to merge latest changes" 183 | exit 1 184 | fi 185 | fi 186 | 187 | # Try to push with retry logic for high-concurrency scenarios 188 | MAX_RETRIES=3 189 | RETRY_COUNT=0 190 | PUSH_SUCCESS=false 191 | 192 | while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do 193 | if git push origin master; then 194 | PUSH_SUCCESS=true 195 | break 196 | fi 197 | 198 | RETRY_COUNT=$((RETRY_COUNT + 1)) 199 | if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then 200 | echo "Push failed (attempt $RETRY_COUNT/$MAX_RETRIES), pulling and retrying..." 201 | git pull --rebase origin master || git pull --no-rebase origin master 202 | sleep 2 203 | fi 204 | done 205 | 206 | if [ "$PUSH_SUCCESS" = false ]; then 207 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 208 | echo "ERROR: Push failed due to branch protection rules" 209 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 210 | echo "" 211 | echo "Choose ONE of these solutions:" 212 | echo "" 213 | echo "✅ OPTION 1 (Recommended): Allow github-actions[bot] to push" 214 | echo " 1. Go to: Settings → Branches → Branch protection rule for 'master'" 215 | echo " 2. Find: 'Restrict who can push to matching branches'" 216 | echo " 3. Click 'Add apps and users'" 217 | echo " 4. Add: github-actions[bot]" 218 | echo " 5. Save changes" 219 | echo "" 220 | echo "✅ OPTION 2: Use a Personal Access Token" 221 | echo " 1. Create a fine-grained PAT at: https://github.com/settings/tokens?type=beta" 222 | echo " 2. Give it 'contents: write' permission for this repository" 223 | echo " 3. Add it as a repository secret named 'PAT_TOKEN'" 224 | echo " 4. The workflow will automatically detect and use it" 225 | echo "" 226 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 227 | exit 1 228 | fi 229 | 230 | echo "Successfully processed contribution for @$username" 231 | echo "success=true" >> $GITHUB_OUTPUT 232 | 233 | - name: Rollback on failure 234 | if: failure() 235 | run: | 236 | echo "Rolling back changes due to processing failure" 237 | 238 | # Log failed entry to repository 239 | mkdir -p .github/failed-entries 240 | TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S") 241 | FAILED_LOG=".github/failed-entries/${TIMESTAMP}_${{ github.sha }}.md" 242 | 243 | cat > "$FAILED_LOG" << 'FAILED_EOF' 244 | # Failed Post-Merge Processing 245 | 246 | **Timestamp:** $TIMESTAMP UTC 247 | **Commit SHA:** ${{ github.sha }} 248 | **PR Number:** (extracted from commit message if available) 249 | **Workflow Run:** https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 250 | 251 | ## Entry Content 252 | 253 | ``` 254 | ENTRY_CONTENT_PLACEHOLDER 255 | ``` 256 | 257 | ## Failure Reason 258 | 259 | Post-merge processing failed. Check workflow logs for details. 260 | 261 | ## Action Required 262 | 263 | Maintainer should: 264 | 1. Review workflow logs 265 | 2. Fix any issues 266 | 3. Manually process this entry or re-run workflow 267 | FAILED_EOF 268 | 269 | # Capture entry content if ADD_YOUR_NAME.md.backup exists 270 | if [ -f "ADD_YOUR_NAME.md.backup" ]; then 271 | ENTRY_CONTENT=$(sed -n '/Add your entry below this line/,$p' ADD_YOUR_NAME.md.backup) 272 | # Escape for sed (basic escaping) 273 | ENTRY_CONTENT_ESCAPED=$(echo "$ENTRY_CONTENT" | sed 's/[\/&]/\\&/g') 274 | sed -i "s|ENTRY_CONTENT_PLACEHOLDER|$ENTRY_CONTENT_ESCAPED|g" "$FAILED_LOG" 275 | fi 276 | 277 | # Restore backup files if they exist 278 | if [ -f "CONTRIBUTORS.md.backup" ]; then 279 | mv CONTRIBUTORS.md.backup CONTRIBUTORS.md 280 | echo "Restored CONTRIBUTORS.md from backup" 281 | fi 282 | 283 | if [ -f "README.md.backup" ]; then 284 | mv README.md.backup README.md 285 | echo "Restored README.md from backup" 286 | fi 287 | 288 | if [ -f "ADD_YOUR_NAME.md.backup" ]; then 289 | mv ADD_YOUR_NAME.md.backup ADD_YOUR_NAME.md 290 | echo "Restored ADD_YOUR_NAME.md from backup" 291 | fi 292 | 293 | # Reset to backup commit if available and configure git 294 | if [ -f ".git-backup-commit" ]; then 295 | backup_commit=$(cat .git-backup-commit) 296 | git reset --hard "$backup_commit" 297 | 298 | # Add failed entry log to git 299 | git add .github/failed-entries/ 300 | 301 | # Configure git with token for rollback push (use PAT if available) 302 | GIT_TOKEN="${{ secrets.PAT_TOKEN || github.token }}" 303 | git remote set-url origin https://x-access-token:${GIT_TOKEN}@github.com/${{ github.repository }}.git 304 | git config user.name "Sashank Bhamidi" 305 | git config user.email "hello@sashank.wiki" 306 | 307 | # Commit failed entry log 308 | git commit -m "Log failed post-merge processing for ${{ github.sha }}" || echo "No failed entry to commit" 309 | 310 | # Try to push the rollback (won't work if branch protection blocks it, but we tried) 311 | if git push --force-with-lease origin master; then 312 | echo "Successfully reset repository to backup commit: $backup_commit" 313 | echo "Failed entry logged to $FAILED_LOG" 314 | else 315 | echo "Failed to push rollback - manual intervention required" 316 | echo "Backup commit SHA: $backup_commit" 317 | exit 1 318 | fi 319 | fi 320 | 321 | # Clean up backup files 322 | rm -f CONTRIBUTORS.md.backup README.md.backup ADD_YOUR_NAME.md.backup .git-backup-commit 323 | 324 | - name: Create failure issue 325 | if: failure() 326 | run: | 327 | # Get the entry details if available 328 | entry="${{ steps.process.outputs.entry }}" 329 | username="${{ steps.process.outputs.username }}" 330 | name="${{ steps.process.outputs.name }}" 331 | 332 | # Create issue body in a temporary file to avoid YAML escaping issues 333 | cat > /tmp/issue-body.md << 'ISSUE_EOF' 334 | ## Post-merge automation failed 335 | 336 | **Status:** The merge was reverted automatically via rollback mechanism. 337 | 338 | ### What happened 339 | The post-merge processing workflow encountered an error and failed. The repository has been automatically rolled back to its previous state. 340 | 341 | ### Contribution Details 342 | - **Entry:** `ENTRY_PLACEHOLDER` 343 | - **Username:** @USERNAME_PLACEHOLDER 344 | - **Name:** NAME_PLACEHOLDER 345 | - **PR:** PR_PLACEHOLDER 346 | 347 | ### What was done automatically 348 | - Restored backup files (CONTRIBUTORS.md, README.md, ADD_YOUR_NAME.md) 349 | - Reset repository to pre-merge commit 350 | - Force pushed rollback to master 351 | 352 | ### Workflow Run 353 | - **Run ID:** RUN_ID_PLACEHOLDER 354 | - **Direct link:** https://github.com/REPO_PLACEHOLDER/actions/runs/RUN_ID_PLACEHOLDER 355 | ISSUE_EOF 356 | 357 | # Replace placeholders with actual values (escape newlines in PR message) 358 | pr_message=$(printf '%s' '${{ github.event.head_commit.message }}' | head -n 1) 359 | sed -i "s|ENTRY_PLACEHOLDER|${entry:-Not captured}|g" /tmp/issue-body.md 360 | sed -i "s|USERNAME_PLACEHOLDER|${username:-unknown}|g" /tmp/issue-body.md 361 | sed -i "s|NAME_PLACEHOLDER|${name:-Not captured}|g" /tmp/issue-body.md 362 | sed -i "s|PR_PLACEHOLDER|${pr_message}|g" /tmp/issue-body.md 363 | sed -i "s|RUN_ID_PLACEHOLDER|${{ github.run_id }}|g" /tmp/issue-body.md 364 | sed -i "s|REPO_PLACEHOLDER|${{ github.repository }}|g" /tmp/issue-body.md 365 | 366 | gh issue create --title "Post-merge workflow failed" --body-file /tmp/issue-body.md --label "urgent,workflow-failure" --assignee SashankBhamidi 367 | env: 368 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 369 | 370 | - name: Report processing completion 371 | if: always() 372 | run: | 373 | echo "Post-merge processing completed" 374 | -------------------------------------------------------------------------------- /.github/workflows/process-contribution.yml: -------------------------------------------------------------------------------- 1 | name: Process Contribution 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | branches: [master] 7 | pull_request_review: 8 | types: [submitted] 9 | 10 | concurrency: 11 | group: contribution-processing-${{ github.event.pull_request.number || github.run_id }} 12 | cancel-in-progress: false 13 | 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | issues: write 18 | actions: read 19 | checks: read 20 | statuses: write 21 | 22 | jobs: 23 | validate-and-process: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: write 27 | pull-requests: write 28 | issues: write 29 | actions: read 30 | checks: read 31 | env: 32 | PR_NUMBER: ${{ github.event.pull_request.number || github.event.number || 0 }} 33 | PR_AUTHOR: ${{ github.event.pull_request.user.login || 'unknown' }} 34 | 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 38 | with: 39 | fetch-depth: 0 40 | ref: ${{ github.event.pull_request.head.sha || github.sha }} 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Setup Node.js 44 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 45 | with: 46 | node-version: "18" 47 | 48 | - name: Fetch master branch 49 | run: git fetch origin master:master 50 | 51 | - name: Check only ADD_YOUR_NAME.md is modified 52 | id: file-check 53 | run: | 54 | # Check if ADD_YOUR_NAME.md was renamed 55 | if git diff --name-status master...${{ github.event.pull_request.head.sha || github.sha }} | grep -q "^R.*ADD_YOUR_NAME.md"; then 56 | echo "valid=false" >> $GITHUB_OUTPUT 57 | echo "is_contribution=false" >> $GITHUB_OUTPUT 58 | echo "error_message=DO NOT rename ADD_YOUR_NAME.md! Edit the file directly, don't rename it to your name." >> $GITHUB_OUTPUT 59 | exit 0 60 | fi 61 | 62 | files_changed=$(git diff --name-only master...${{ github.event.pull_request.head.sha || github.sha }}) 63 | file_count=$(echo "$files_changed" | wc -l) 64 | files_list=$(echo "$files_changed" | tr '\n' ', ' | sed 's/,/, /g' | sed 's/, $//') 65 | 66 | if [ "$files_changed" = "ADD_YOUR_NAME.md" ] && [ "$file_count" = "1" ]; then 67 | echo "valid=true" >> $GITHUB_OUTPUT 68 | echo "is_contribution=true" >> $GITHUB_OUTPUT 69 | elif echo "$files_changed" | grep -q "ADD_YOUR_NAME.md"; then 70 | echo "valid=false" >> $GITHUB_OUTPUT 71 | echo "is_contribution=true" >> $GITHUB_OUTPUT 72 | echo "error_message=Only ADD_YOUR_NAME.md should be modified for contributions. Found changes in: $files_list" >> $GITHUB_OUTPUT 73 | else 74 | echo "valid=false" >> $GITHUB_OUTPUT 75 | echo "is_contribution=false" >> $GITHUB_OUTPUT 76 | echo "error_message=This PR does not appear to be a contribution. No changes to ADD_YOUR_NAME.md found." >> $GITHUB_OUTPUT 77 | fi 78 | 79 | - name: Link PR to an open contribution issue 80 | if: steps.file-check.outputs.is_contribution == 'true' && github.event.pull_request.user.login != 'SashankBhamidi' 81 | continue-on-error: true 82 | run: | 83 | set +e # Don't exit on error, we'll handle errors gracefully 84 | 85 | # Get current PR body first 86 | CURRENT_BODY=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq '.body // ""' 2>/dev/null) 87 | if [ $? -ne 0 ]; then 88 | echo "Warning: Could not fetch PR body, skipping issue linking" 89 | exit 0 90 | fi 91 | 92 | # Check if PR body already has closing keywords for a contribution issue 93 | HAS_CONTRIBUTION_LINK=false 94 | if echo "$CURRENT_BODY" | grep -qiE '(close[sd]?|fix(e[sd])?|resolve[sd]?) #[0-9]+'; then 95 | # Extract the issue number 96 | LINKED_ISSUE=$(echo "$CURRENT_BODY" | grep -oiE '(close[sd]?|fix(e[sd])?|resolve[sd]?) #[0-9]+' | grep -oE '#[0-9]+' | head -1 | tr -d '#') 97 | 98 | # Check if the linked issue has "contribution" label 99 | LABELS=$(gh api "repos/${{ github.repository }}/issues/$LINKED_ISSUE" --jq '.labels[].name' 2>/dev/null || echo "") 100 | if echo "$LABELS" | grep -q "contribution"; then 101 | HAS_CONTRIBUTION_LINK=true 102 | echo "PR already links to contribution issue #$LINKED_ISSUE" 103 | fi 104 | fi 105 | 106 | # Only find and link an issue if no contribution issue is already linked 107 | if [ "$HAS_CONTRIBUTION_LINK" = false ]; then 108 | # Get all open contribution issues (sorted oldest first) 109 | OPEN_ISSUES=$(gh issue list --label "contribution" --state open --json number,createdAt --jq 'sort_by(.createdAt) | .[].number' 2>/dev/null) 110 | if [ $? -ne 0 ] || [ -z "$OPEN_ISSUES" ]; then 111 | echo "Warning: Could not fetch open issues or no issues available" 112 | exit 0 113 | fi 114 | 115 | # Get all open PRs and their linked issues 116 | LINKED_ISSUES=$(gh pr list --state open --json body --jq '.[].body' 2>/dev/null | grep -oiE '(close[sd]?|fix(e[sd])?|resolve[sd]?) #[0-9]+' | grep -oE '#[0-9]+' | tr -d '#' | sort -u) 117 | 118 | # Find an unassigned issue 119 | UNASSIGNED_ISSUE="" 120 | for issue in $OPEN_ISSUES; do 121 | if ! echo "$LINKED_ISSUES" | grep -q "^${issue}$"; then 122 | UNASSIGNED_ISSUE=$issue 123 | echo "Found unassigned contribution issue: #$UNASSIGNED_ISSUE" 124 | break 125 | fi 126 | done 127 | 128 | if [ -n "$UNASSIGNED_ISSUE" ]; then 129 | echo "$CURRENT_BODY" > /tmp/pr-body.txt 130 | echo "" >> /tmp/pr-body.txt 131 | echo "Resolves #${UNASSIGNED_ISSUE}" >> /tmp/pr-body.txt 132 | if gh pr edit $PR_NUMBER --body-file /tmp/pr-body.txt 2>/dev/null; then 133 | echo "Linked PR to unassigned contribution issue #$UNASSIGNED_ISSUE" 134 | else 135 | echo "Warning: Could not link issue, but continuing workflow" 136 | fi 137 | else 138 | echo "⚠ All contribution issues are already assigned to other PRs" 139 | fi 140 | fi 141 | env: 142 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 143 | 144 | - name: Check for maintainer approval 145 | id: check-approval 146 | if: steps.file-check.outputs.valid == 'true' 147 | run: | 148 | # Check if PR has been approved by a maintainer (SashankBhamidi) 149 | APPROVALS=$(gh pr view $PR_NUMBER --json reviews --jq '[.reviews[] | select(.state == "APPROVED" and .author.login == "SashankBhamidi")] | length') 150 | 151 | if [ "$APPROVALS" -gt 0 ]; then 152 | echo "maintainer_approved=true" >> $GITHUB_OUTPUT 153 | echo "PR has maintainer approval - will bypass profanity check" 154 | else 155 | echo "maintainer_approved=false" >> $GITHUB_OUTPUT 156 | echo "No maintainer approval found" 157 | fi 158 | env: 159 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 160 | 161 | - name: Comprehensive validation and profanity check 162 | id: validate 163 | if: steps.file-check.outputs.valid == 'true' 164 | run: node scripts/comprehensive-validation.js || true 165 | 166 | - name: Verify GitHub username matches PR author 167 | if: steps.file-check.outputs.valid == 'true' && steps.validate.outputs.valid == 'true' && (steps.validate.outputs.profanity_detected != 'true' || steps.check-approval.outputs.maintainer_approved == 'true') 168 | id: username-check 169 | run: | 170 | ENTRY_USERNAME="${{ steps.validate.outputs.username }}" 171 | 172 | # Case-insensitive comparison (GitHub usernames are case-insensitive) 173 | PR_AUTHOR_LOWER=$(echo "$PR_AUTHOR" | tr '[:upper:]' '[:lower:]') 174 | ENTRY_USERNAME_LOWER=$(echo "$ENTRY_USERNAME" | tr '[:upper:]' '[:lower:]') 175 | 176 | if [ "$PR_AUTHOR_LOWER" != "$ENTRY_USERNAME_LOWER" ]; then 177 | echo "valid=false" >> $GITHUB_OUTPUT 178 | echo "error=Username mismatch: Your GitHub username is @$PR_AUTHOR but you entered @$ENTRY_USERNAME. Please use your own GitHub username." >> $GITHUB_OUTPUT 179 | echo "Username validation failed" 180 | else 181 | echo "valid=true" >> $GITHUB_OUTPUT 182 | echo "Username matches PR author" 183 | fi 184 | 185 | - name: Approve and comment on valid contribution 186 | if: steps.file-check.outputs.valid == 'true' && steps.validate.outputs.valid == 'true' && (steps.validate.outputs.profanity_detected != 'true' || steps.check-approval.outputs.maintainer_approved == 'true') && steps.username-check.outputs.valid == 'true' 187 | run: | 188 | # Skip comment for maintainer PRs 189 | if [ "$PR_AUTHOR" = "SashankBhamidi" ]; then 190 | echo "Skipping comment for maintainer PR" 191 | else 192 | # Create comment body in temp file to handle special characters 193 | cat > /tmp/approval-comment.md << 'COMMENT_EOF' 194 | ## Approved - Ready to Merge 195 | 196 | Your entry looks good: 197 | ``` 198 | ENTRY_PLACEHOLDER 199 | ``` 200 | 201 | ### Validation Results 202 | - Format is correct 203 | - No duplicates found 204 | - Content looks clean 205 | - Username matches PR author 206 | 207 | This PR is **approved** and will be automatically merged. Once merged, you'll be added to the [contributors list](https://github.com/SashankBhamidi/git-gang/blob/master/CONTRIBUTORS.md). Welcome to the Git Gang! 208 | COMMENT_EOF 209 | 210 | # Replace placeholders 211 | sed -i "s|ENTRY_PLACEHOLDER|${{ steps.validate.outputs.entry }}|g" /tmp/approval-comment.md 212 | 213 | # Try to comment (may fail if already exists or rate limited) 214 | gh pr comment $PR_NUMBER --body-file /tmp/approval-comment.md || echo "Comment skipped" 215 | fi 216 | 217 | # Remove invalid/moderation labels if they exist (user fixed their entry or maintainer approved) 218 | gh pr edit $PR_NUMBER --remove-label "invalid" 2>/dev/null || true 219 | gh pr edit $PR_NUMBER --remove-label "moderation" 2>/dev/null || true 220 | 221 | # Add approved label (may fail on review events due to permissions) 222 | gh pr edit $PR_NUMBER --add-label "approved" 2>/dev/null || echo "Label update skipped" 223 | 224 | # Add Hacktoberfest label if it's October 225 | CURRENT_MONTH=$(date +%m) 226 | if [ "$CURRENT_MONTH" = "10" ]; then 227 | echo "October detected - adding hacktoberfest-accepted label" 228 | gh pr edit $PR_NUMBER --add-label "hacktoberfest-accepted" 2>/dev/null || true 229 | fi 230 | 231 | # Auto-merge approved contributions (critical - must succeed) 232 | echo "Auto-merging approved contribution..." 233 | gh pr merge $PR_NUMBER --squash --auto 234 | echo "Auto-merge enabled - PR will merge when all checks pass" 235 | env: 236 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 237 | 238 | - name: Create moderation issue for profanity 239 | if: steps.validate.outputs.profanity_detected == 'true' && steps.check-approval.outputs.maintainer_approved != 'true' 240 | id: moderation-issue 241 | run: | 242 | # Create issue body in temp file to handle special characters 243 | cat > /tmp/moderation-issue.md << 'ISSUE_EOF' 244 | Content filter flagged this entry before merge. 245 | 246 | - Entry: ENTRY_PLACEHOLDER 247 | - Username: @USERNAME_PLACEHOLDER 248 | - PR: #PR_NUMBER_PLACEHOLDER 249 | - Name: NAME_PLACEHOLDER 250 | - Message: MESSAGE_PLACEHOLDER 251 | 252 | Flagged in name: NAME_FLAG_PLACEHOLDER 253 | Flagged in message: MESSAGE_FLAG_PLACEHOLDER 254 | 255 | Take a look and decide what to do. Approving and merging the PR will automatically close this issue. 256 | 257 | - [ ] Reviewed 258 | - [ ] Handled 259 | ISSUE_EOF 260 | 261 | # Replace placeholders 262 | sed -i "s|ENTRY_PLACEHOLDER|${{ steps.validate.outputs.entry }}|g" /tmp/moderation-issue.md 263 | sed -i "s|USERNAME_PLACEHOLDER|${{ steps.validate.outputs.username }}|g" /tmp/moderation-issue.md 264 | sed -i "s|PR_NUMBER_PLACEHOLDER|$PR_NUMBER|g" /tmp/moderation-issue.md 265 | sed -i "s|NAME_PLACEHOLDER|${{ steps.validate.outputs.name }}|g" /tmp/moderation-issue.md 266 | sed -i "s|MESSAGE_PLACEHOLDER|${{ steps.validate.outputs.message }}|g" /tmp/moderation-issue.md 267 | sed -i "s|NAME_FLAG_PLACEHOLDER|${{ steps.validate.outputs.profanity_in_name }}|g" /tmp/moderation-issue.md 268 | sed -i "s|MESSAGE_FLAG_PLACEHOLDER|${{ steps.validate.outputs.profanity_in_message }}|g" /tmp/moderation-issue.md 269 | 270 | # Create issue and capture the issue number 271 | ISSUE_URL=$(gh issue create --title "Review entry from @${{ steps.validate.outputs.username }}" \ 272 | --body-file /tmp/moderation-issue.md \ 273 | --label "moderation,urgent" \ 274 | --assignee SashankBhamidi) 275 | 276 | ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -oE '[0-9]+$') 277 | echo "issue_number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT 278 | echo "Created moderation issue #$ISSUE_NUMBER" 279 | 280 | # Update PR body to link the moderation issue (will auto-close when PR is merged/closed) 281 | CURRENT_BODY=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq '.body // ""') 282 | echo "$CURRENT_BODY" > /tmp/pr-body-update.txt 283 | echo "" >> /tmp/pr-body-update.txt 284 | echo "Closes #${ISSUE_NUMBER}" >> /tmp/pr-body-update.txt 285 | gh pr edit $PR_NUMBER --body-file /tmp/pr-body-update.txt 286 | echo "Linked moderation issue #$ISSUE_NUMBER to PR #$PR_NUMBER" 287 | env: 288 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 289 | 290 | - name: Request review for unauthorized file changes 291 | if: steps.file-check.outputs.valid == 'false' && steps.file-check.outputs.is_contribution == 'true' 292 | run: | 293 | gh pr edit $PR_NUMBER --add-reviewer SashankBhamidi 294 | env: 295 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 296 | 297 | - name: Handle non-contribution PRs 298 | if: steps.file-check.outputs.is_contribution == 'false' && github.event.pull_request.user.login != 'SashankBhamidi' 299 | run: | 300 | printf '%s\n' \ 301 | '## Not a contribution' \ 302 | '' \ 303 | '**Issue:** ${{ steps.file-check.outputs.error_message }}' \ 304 | '' \ 305 | 'If you meant to add your name, please make sure to edit ADD_YOUR_NAME.md.' \ 306 | > /tmp/non-contribution-comment.md 307 | gh pr comment $PR_NUMBER --body-file /tmp/non-contribution-comment.md 308 | gh pr edit $PR_NUMBER --add-label "not-a-contribution" 309 | env: 310 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 311 | 312 | - name: Comment on validation issues 313 | if: | 314 | (steps.file-check.outputs.valid == 'false' || 315 | steps.validate.outputs.valid == 'false' || 316 | (steps.validate.outputs.profanity_detected == 'true' && steps.check-approval.outputs.maintainer_approved != 'true') || 317 | steps.username-check.outputs.valid == 'false') && 318 | steps.file-check.outputs.is_contribution == 'true' && 319 | github.event.pull_request.user.login != 'SashankBhamidi' 320 | run: | 321 | if [ "${{ steps.file-check.outputs.valid }}" == "false" ]; then 322 | # Use printf to avoid heredoc issues with variable substitution 323 | printf '%s\n' \ 324 | '## Unauthorized File Changes Detected' \ 325 | '' \ 326 | '**Issue:** ${{ steps.file-check.outputs.error_message }}' \ 327 | '' \ 328 | '### What you need to do' \ 329 | 'Contributions should **only** modify `ADD_YOUR_NAME.md`. Please:' \ 330 | '1. Remove changes to other files' \ 331 | '2. Keep only your entry in `ADD_YOUR_NAME.md`' \ 332 | '3. Push the update' \ 333 | '' \ 334 | 'This PR has been flagged for manual review.' \ 335 | > /tmp/error-comment.md 336 | gh pr comment $PR_NUMBER --body-file /tmp/error-comment.md 337 | gh pr edit $PR_NUMBER --add-label "unauthorized" 338 | elif [ "${{ steps.validate.outputs.valid }}" == "false" ]; then 339 | # Check if it's a duplicate entry 340 | if echo "${{ steps.validate.outputs.error_message }}" | grep -q "already been added"; then 341 | printf '%s\n' \ 342 | '## Already a Contributor' \ 343 | '' \ 344 | "Looks like you've already joined the Git Gang - your name is already in the [contributors list](https://github.com/SashankBhamidi/git-gang/blob/master/CONTRIBUTORS.md)." \ 345 | '' \ 346 | 'Thanks for your enthusiasm, but we can only add each person once. Closing this PR.' \ 347 | > /tmp/duplicate-comment.md 348 | gh pr comment $PR_NUMBER --body-file /tmp/duplicate-comment.md 349 | gh pr edit $PR_NUMBER --add-label "duplicate" 350 | gh pr close $PR_NUMBER 351 | else 352 | printf '%s\n' \ 353 | '## Validation Failed - Fix Required' \ 354 | '' \ 355 | "There's an issue with your entry:" \ 356 | '' \ 357 | '**Issue:** ${{ steps.validate.outputs.error_message }}' \ 358 | '' \ 359 | '### How to fix' \ 360 | '1. Click on the **Files changed** tab' \ 361 | '2. Click the pencil icon to edit `ADD_YOUR_NAME.md`' \ 362 | '3. Fix the issue mentioned above' \ 363 | '4. Commit the changes' \ 364 | '' \ 365 | 'The validation will run again automatically when you push the update.' \ 366 | > /tmp/fix-comment.md 367 | gh pr comment $PR_NUMBER --body-file /tmp/fix-comment.md 368 | gh pr edit $PR_NUMBER --add-label "invalid" 369 | fi 370 | elif [ "${{ steps.username-check.outputs.valid }}" == "false" ]; then 371 | printf '%s\n' \ 372 | '## Username Mismatch' \ 373 | '' \ 374 | 'Your GitHub username is **@'"$PR_AUTHOR"'** but you entered **@${{ steps.validate.outputs.username }}**.' \ 375 | '' \ 376 | '### How to fix' \ 377 | 'Please update your entry in `ADD_YOUR_NAME.md` to use your own GitHub username: **@'"$PR_AUTHOR"'**' \ 378 | '' \ 379 | 'You cannot submit entries on behalf of others.' \ 380 | > /tmp/username-comment.md 381 | gh pr comment $PR_NUMBER --body-file /tmp/username-comment.md 382 | gh pr edit $PR_NUMBER --add-label "invalid" 383 | elif [ "${{ steps.validate.outputs.profanity_detected }}" == "true" ]; then 384 | printf '%s\n' \ 385 | '## Flagged for Manual Review' \ 386 | '' \ 387 | 'Your entry was flagged by our content filter and needs manual review.' \ 388 | '' \ 389 | '### What happens next' \ 390 | '- A maintainer will review your entry shortly' \ 391 | '- If approved, your PR will be merged automatically' \ 392 | "- You'll be notified of the decision" \ 393 | '' \ 394 | 'Please be patient while we review. Thank you!' \ 395 | > /tmp/moderation-comment.md 396 | gh pr comment $PR_NUMBER --body-file /tmp/moderation-comment.md 397 | gh pr edit $PR_NUMBER --add-label "moderation" 398 | fi 399 | env: 400 | GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} 401 | 402 | - name: Report workflow completion 403 | if: always() 404 | run: | 405 | echo "Workflow completed for PR #$PR_NUMBER" 406 | --------------------------------------------------------------------------------