Enforce staging-first workflows by ensuring commits come from specified branches before deployment
This GitHub Action helps maintain deployment discipline by verifying that commits in your main branch actually originated from your staging or development branches. Perfect for preventing direct pushes to production and enforcing proper code review workflows.
- π Smart Merge Detection - Automatically handles GitHub merge commits from pull requests
- β° Minimum Age Enforcement - Ensure code has "soaked" in staging for a specified time
- π― Multi-Branch Support - Verify against multiple required branches (staging, dev, etc.)
- β‘ Fast & Reliable - Uses native Git commands with comprehensive error handling
- π οΈ Highly Configurable - Customize behavior for your specific workflow
- π Detailed Outputs - Get verification results and metadata for downstream jobs
- π§ Debug Mode - Verbose logging to troubleshoot complex scenarios
name: Deploy to Production
on:
push:
branches: [main]
jobs:
verify-staging:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for proper branch comparison
- name: Verify commit came from staging
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'Ensure code has been stable in staging for at least 1 hour before production:
- name: Verify staging with soak time
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
minimum-age: '1h' # Must be in staging for 1 hour
fail-on-missing: true- name: Verify staging-first workflow
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging,development' # Multiple branches
minimum-age: '30m' # 30 minute soak time
check-merge-commits: true # Handle PR merges
fail-on-missing: true # Fail workflow if not found
verbose: true # Enable debug output
timezone: 'America/New_York' # Time calculations timezoneEnsure all production deployments come through your staging environment:
name: Production Deploy
on:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure staging-first workflow
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
deploy:
needs: verify
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.shVerify commits passed through development AND staging:
- name: Verify multi-stage workflow
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'development,staging'
fail-on-missing: truePerfect for organizations requiring stability periods before production:
name: Production Deploy with Soak Time
on:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure 2-hour staging soak time
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
minimum-age: '2h'
verbose: true
deploy:
needs: verify
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.shPrevent fresh code from going to production on Fridays:
- name: Weekend deploy protection
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
minimum-age: '1d' # Must be in staging for 1 day
fail-on-missing: trueUse outputs to create conditional deployment logic:
- name: Check branch ancestry
id: verify
uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
fail-on-missing: false
- name: Deploy if verified
if: steps.verify.outputs.verified == 'true'
run: ./deploy.sh
- name: Send alert if not verified
if: steps.verify.outputs.verified == 'false'
run: ./alert-team.sh "Direct push detected!"| Input | Description | Required | Default |
|---|---|---|---|
required-branches |
Comma-separated list of branches that must contain the commit | β | staging |
current-branch |
Current branch being checked (auto-detected if not provided) | β | Auto-detected |
fail-on-missing |
Whether to fail workflow if commit not found in required branches | β | true |
check-merge-commits |
Whether to validate merge commits by checking their parents | β | true |
verbose |
Enable detailed logging for debugging | β | false |
minimum-age |
Minimum time commit must exist in required branches (e.g., "1h", "30m", "2d") | β | 0m |
timezone |
Timezone for time calculations (e.g., "UTC", "America/New_York") | β | UTC |
github-token |
GitHub token for API access | β | ${{ github.token }} |
| Output | Description |
|---|---|
verified |
Whether the commit verification passed (true/false) |
commit-sha |
The commit SHA that was verified |
source-branch |
The branch from which the commit originated |
commit-age |
Age of the commit in the source branch (human readable, e.g., "2h 15m") |
minimum-age-met |
Whether the minimum age requirement was met (true/false) |
The minimum-age input supports flexible time formats:
minimum-age: '30m' # 30 minutes
minimum-age: '1h' # 1 hour
minimum-age: '2h30m' # 2 hours 30 minutes
minimum-age: '1d' # 1 day
minimum-age: '1w' # 1 week
minimum-age: '2d4h' # 2 days 4 hoursSupported units: s (seconds), m (minutes), h (hours), d (days), w (weeks)
For regular commits, the action uses git merge-base --is-ancestor to verify the commit exists in the specified branch(es).
When GitHub merges a pull request, it creates a merge commit with two parents:
- Parent 1: The previous HEAD of the target branch (e.g., main)
- Parent 2: The HEAD of the source branch (e.g., staging)
The action intelligently detects merge commits and verifies that at least one parent originated from your required branches.
- Your workflow must use
fetch-depth: 0to ensure full Git history is available - Required branches must exist and be accessible
- Proper permissions for the GitHub token (usually automatic)
β "Failed to fetch branch 'staging'"
- Ensure the branch exists in your repository
- Check that the branch name is spelled correctly
- Verify repository permissions
β "Commit not found in any required branches"
- This usually means the commit was pushed directly to main
- Use
verbose: trueto get detailed debugging information - Check that your staging branch is up to date
β "shallow repository" errors
- Add
fetch-depth: 0to your checkout action - This ensures the full Git history is available
Enable verbose logging to troubleshoot issues:
- uses: codad5/verify-branch-ancestry@v1
with:
required-branches: 'staging'
verbose: trueThis provides detailed information about:
- Branch heads and commit SHAs
- Merge base calculations
- Parent commit analysis
- Step-by-step verification process
We welcome contributions! Please see our Contributing Guide for details.
- Clone the repository
- Make your changes to
action.yml - Test with the provided workflow examples
- Submit a pull request
# Test the action in a local repository
git checkout main
export GITHUB_SHA=$(git rev-parse HEAD)
export GITHUB_REF=refs/heads/main
bash -c "$(cat action.yml | yq '.runs.steps[0].run')"Check out our examples directory for complete workflow configurations:
- Basic staging-first workflow
- Multi-environment validation
- Conditional deployment
- Custom error handling
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by the need for better deployment discipline
- Thanks to the GitHub Actions community for best practices
- Built with β€οΈ for developers who care about code quality
Like this action? β Give it a star and share with your team!
Need help? π¬ Open an issue or start a discussion