如何仅允许从testing分支向master分支合并提交?
Great question—you absolutely can set up a server-side hook to block pushes to master that aren't merge commits originating from your testing branch. While you're right that the pre-receive hook doesn't explicitly expose the "source branch" of a merge commit, we can work around this by inspecting the commit's parent history and verifying it ties back to testing.
How It Works
The pre-receive hook gets passed three pieces of data for each ref being pushed: the old SHA, new SHA, and ref name. For pushes targeting master, we'll:
- Reject any direct (non-merge) commits to
master—only merge commits are allowed. - For merge commits, check if at least one of their parent commits exists in the
testingbranch's history (confirming the merge came fromtesting).
Example Pre-Receive Hook Script
Create a pre-receive file in your server repository's .git/hooks directory (remove the .sample suffix if it exists) with this code:
#!/bin/bash # Configure your protected and allowed branches PROTECTED_BRANCH="refs/heads/master" ALLOWED_SOURCE="refs/heads/testing" # Read each line of input from Git (old_sha new_sha ref_name) while read old_sha new_sha ref_name; do # Skip if we're not dealing with the master branch if [ "$ref_name" != "$PROTECTED_BRANCH" ]; then continue fi # Get the latest commit SHA of the testing branch testing_head=$(git rev-parse "$ALLOWED_SOURCE") # Iterate over all commits being pushed to master for commit in $(git rev-list "$old_sha..$new_sha"); do # Check if this is a merge commit (has 2+ parent commits) parent_count=$(git rev-list --parents -n 1 "$commit" | wc -w) if [ "$parent_count" -lt 2 ]; then echo "❌ ERROR: Direct commits to master are forbidden. Only merge commits from testing are allowed." >&2 exit 1 fi # Get all parent commits of the merge parents=$(git rev-list --parents -n 1 "$commit" | cut -d' ' -f2-) # Verify at least one parent is part of the testing branch history valid_merge=0 for parent in $parents; do if git merge-base --is-ancestor "$parent" "$ALLOWED_SOURCE"; then valid_merge=1 break fi done if [ "$valid_merge" -eq 0 ]; then echo "❌ ERROR: Merge commit $commit to master does not originate from the testing branch. Only merges from testing are permitted." >&2 exit 1 fi done done # If all checks pass, allow the push exit 0
Setup Steps
- Make the script executable:
chmod +x .git/hooks/pre-receive - Test it by trying to push a direct commit to
masteror a merge from a branch other thantesting—the hook should reject these pushes with clear error messages.
Key Notes
- No direct commits: This script blocks any non-merge commits to
master, ensuring all changes come throughtestingvia merges. - Flexible validation: Using
git merge-base --is-ancestorchecks if the parent commit is anywhere in thetestingbranch's history, not just the latest HEAD—so even if you merge a slightly oldertestingstate, it will still be allowed. - Emergency exceptions: If you ever need to bypass the rule (e.g., critical hotfix), you could add a check for a specific keyword in the commit message (like
[EMERGENCY]) to allow those pushes—just be cautious with this to avoid breaking your workflow.
You were right to wonder about the lack of explicit source branch data in the pre-receive hook, but by leveraging Git's commit history tools, we can reliably enforce your desired workflow.
内容的提问来源于stack exchange,提问作者VP.

