Skip to content

receive-pack Git Command Guide

The git receive-pack command receives what is pushed into the repository and updates it with information fed from the remote end. It is invoked by git send-pack and handles the server-side operations for push protocols.

Terminal window
git receive-pack <git-dir>
ParameterDescription
<git-dir>Path to the Git directory/repository
Push Protocol Sequence:
1. Client (send-pack) negotiates with server
2. Server (receive-pack) advertises refs
3. Client determines what to send
4. Client sends packfile with objects
5. Server receives and validates packfile
6. Server updates references
7. Server runs hooks (pre/post-receive)
8. Server sends success/failure response
Communication Flow:
Client ── send-pack ── packfile ──► Server
▲ │
│ ▼
└──────── refs advertisement ◄──────── receive-pack
Reference Update Process:
├── Validate incoming references
├── Check permissions and policies
├── Update refs in transaction
├── Run pre-receive hooks
├── Apply reference changes
├── Run post-receive hooks
├── Send confirmation to client
Safety Mechanisms:
├── Atomic reference updates
├── Hook-based validation
├── Configurable access controls
├── Backup and recovery options
Packfile Reception:
├── Receive compressed packfile
├── Validate packfile integrity
├── Extract objects to .git/objects
├── Update indexes and caches
├── Verify object connectivity
├── Clean up temporary files
Validation Steps:
├── SHA-1 verification
├── Object type checking
├── Reference validity
├── Permission checks
Terminal window
# Configure receive behavior
git config receive.denyNonFastForwards false # Allow non-fast-forward updates
git config receive.denyDeletes false # Allow branch/tag deletion
git config receive.denyDeleteCurrent false # Allow deletion of current branch
git config receive.denyCurrentBranch ignore # Handle current branch updates
# Configure access control
git config receive.fsckObjects true # Verify received objects
git config receive.advertisePushOptions true # Enable push options
git config receive.keepAlive 5 # Connection keep-alive
# Configure hooks
git config core.hooksPath /path/to/hooks # Custom hooks directory
git config receive.procReceiveRefs refs/for/* # Proc-receive refs
Terminal window
# Access control
git config receive.denyNonFastForwards true # Require fast-forwards
git config receive.denyDeletes true # Deny ref deletions
git config receive.certNonceSeed "random-seed" # Certificate validation
# Object validation
git config receive.fsck.skipList /etc/git/fsck-skiplist
git config receive.fsck.unreachable true # Check unreachable objects
# Network security
git config receive.maxInputSize 100m # Limit input size
git config receive.unpackLimit 100 # Unpack limit
# Pre-receive hook example
cat > .git/hooks/pre-receive << 'EOF'
#!/bin/bash
# Validate incoming pushes
while read oldrev newrev refname; do
echo "Validating $refname: $oldrev -> $newrev"
# Check for force pushes
if [ "$oldrev" != "0000000000000000000000000000000000000000" ] &&
! git merge-base --is-ancestor "$oldrev" "$newrev" 2>/dev/null; then
echo "Non-fast-forward push denied on $refname"
exit 1
fi
# Validate commit messages
if ! git rev-list "$oldrev..$newrev" --grep="^[^:]*: " | grep -q .; then
echo "Commits must follow 'Subject: description' format"
exit 1
fi
done
echo "Pre-receive validation passed"
EOF
chmod +x .git/hooks/pre-receive
Terminal window
# Initialize bare repository for receive-pack
git init --bare /path/to/repo.git
# Configure for receive-pack
cd /path/to/repo.git
git config core.bare true
git config receive.denyNonFastForwards false
# Set up initial refs
git update-ref refs/heads/main <initial-commit>
Terminal window
# Manual receive-pack invocation (rare)
git receive-pack /path/to/repo.git < input.pack
# Typical usage via SSH/HTTP
# SSH: git-receive-pack '/path/to/repo.git'
# HTTP: POST to /git-receive-pack endpoint
# Debug receive-pack
GIT_TRACE=1 git receive-pack /path/to/repo.git
Terminal window
# Update references manually
git update-ref refs/heads/feature abc123
# Delete references
git update-ref -d refs/heads/old-branch
# Create tags
git update-ref refs/tags/v1.0 def456
# Atomic reference updates
git update-ref --stdin << EOF
update refs/heads/main abc123 def456
update refs/heads/feature ghi789 jkl012
EOF
Terminal window
# SSH command restriction
# In ~/.ssh/authorized_keys:
command="git-receive-pack '/srv/git/myrepo.git'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa AAAAB3...
# SSH with git-shell
# In /etc/shells: /usr/bin/git-shell
# User shell: /usr/bin/git-shell
# In ~/.git-shell-commands: receive-pack
Terminal window
# Apache configuration for receive-pack
<Location "/git/myrepo.git/git-receive-pack">
AuthType Basic
AuthName "Git Repository"
Require valid-user
SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
</Location>
# Nginx configuration
location ~ /git/(.+)/git-receive-pack$ {
fastcgi_pass unix:/var/run/fcgiwrap.socket;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
include fastcgi_params;
}
# Custom receive-pack wrapper
cat > custom-receive-pack << 'EOF'
#!/bin/bash
# Custom receive-pack with logging
REPO_PATH="$1"
LOG_FILE="/var/log/git-receive.log"
echo "$(date): receive-pack $REPO_PATH by $USER from $SSH_CLIENT" >> "$LOG_FILE"
# Pre-processing
if [ -x ".git/hooks/pre-receive-custom" ]; then
.git/hooks/pre-receive-custom "$REPO_PATH" || exit 1
fi
# Execute receive-pack
exec git receive-pack "$REPO_PATH"
# Post-processing would go here
EOF
chmod +x custom-receive-pack
Terminal window
# Fix repository permissions
fix_repo_permissions() {
local repo_path="$1"
echo "Fixing permissions for $repo_path"
# Set correct ownership
chown -R git:git "$repo_path"
# Set directory permissions
find "$repo_path" -type d -exec chmod 755 {} \;
# Set file permissions
find "$repo_path" -type f -exec chmod 644 {} \;
# Special permissions for hooks
chmod 755 "$repo_path/hooks"/*
chmod 755 "$repo_path/git-daemon-export-ok" 2>/dev/null || true
}
fix_repo_permissions "/srv/git/myrepo.git"
Terminal window
# Debug hook execution
debug_hooks() {
local repo_path="$1"
echo "Debugging hooks in $repo_path"
# Check hook existence and permissions
ls -la "$repo_path/hooks/"
# Test hook execution
if [ -x "$repo_path/hooks/pre-receive" ]; then
echo "Testing pre-receive hook..."
"$repo_path/hooks/pre-receive" < /dev/null
fi
# Check hook logs
if [ -f "/var/log/git-hooks.log" ]; then
tail -20 /var/log/git-hooks.log
fi
}
debug_hooks "/srv/git/myrepo.git"
Terminal window
# Diagnose network problems
diagnose_network() {
echo "Diagnosing network issues"
# Test basic connectivity
nc -z localhost 9418 && echo "Git daemon port open"
# Test SSH access
ssh -T git@server "echo 'SSH access OK'"
# Test HTTP access
curl -I "https://server/git/repo.git/info/refs"
# Check firewall rules
iptables -L | grep 9418
}
diagnose_network
Terminal window
# Recover from corruption
recover_corruption() {
local repo_path="$1"
echo "Attempting repository recovery"
cd "$repo_path" || exit 1
# Check repository status
git fsck --full
# Recover from backup if available
if [ -d "../backup" ]; then
cp -r ../backup/.git/objects/pack/* .git/objects/pack/
fi
# Rebuild indexes
git repack -a -d
# Verify recovery
git fsck --full --strict
}
recover_corruption "/srv/git/myrepo.git"
Terminal window
# Optimize receive-pack performance
optimize_performance() {
local repo_path="$1"
echo "Optimizing receive-pack performance"
cd "$repo_path" || exit 1
# Configure for performance
git config core.deltaBaseCacheLimit 512m
git config pack.threads 4
git config pack.windowMemory 256m
# Optimize pack files
git repack -a -d --threads=4
# Set up bitmap index
git repack -b
# Configure GC
git config gc.auto 1000
git config gc.autopacklimit 50
}
optimize_performance "/srv/git/myrepo.git"
Terminal window
# Handle large push operations
handle_large_pushes() {
echo "Configuring for large push operations"
# Increase memory limits
git config receive.unpackLimit 200
git config pack.packSizeLimit 2g
git config pack.windowMemory 512m
# Configure timeouts
git config receive.keepAlive 30
git config http.postBuffer 524288000 # 500MB
# Set up staging area
export GIT_ALTERNATE_OBJECT_DIRECTORIES=/tmp/git-objects
}
handle_large_pushes
#!/bin/bash
# Enterprise Git server setup with receive-pack
setup_enterprise_git() {
local server_root="/srv/git"
local org_name="$1"
echo "Setting up enterprise Git server for $org_name"
# Create organization directory
mkdir -p "$server_root/$org_name"
cd "$server_root/$org_name"
# Set up base configurations
cat > git-config << EOF
[receive]
denyNonFastForwards = true
denyDeletes = true
denyDeleteCurrent = true
fsckObjects = true
[core]
logAllRefUpdates = true
[hooks]
path = /etc/git/hooks
EOF
# Create repository template
mkdir template.git
cd template.git
git init --bare
# Apply configurations
cp ../git-config config
# Set up hooks
mkdir -p hooks
cat > hooks/pre-receive << 'EOF'
#!/bin/bash
# Enterprise pre-receive hook
# Load organization policies
source /etc/git/policies/$org_name.sh
# Validate push
while read oldrev newrev refname; do
# Check branch naming
if [[ "$refname" =~ ^refs/heads/ ]]; then
branch="${refname#refs/heads/}"
if ! valid_branch_name "$branch"; then
echo "Invalid branch name: $branch"
exit 1
fi
fi
# Check commit signatures
if ! git verify-commit "$newrev" 2>/dev/null; then
echo "Unsigned commits not allowed"
exit 1
fi
# Check file size limits
git rev-list --objects "$oldrev..$newrev" |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize)' |
while read type sha size; do
if [ "$type" = "blob" ] && [ "$size" -gt 104857600 ]; then # 100MB
echo "File too large: $(git rev-list -1 --oneline "$sha")"
exit 1
fi
done
done
echo "Enterprise validation passed"
EOF
chmod +x hooks/pre-receive
cd ..
echo "Enterprise Git server setup complete"
}
setup_enterprise_git "acme-corp"
Terminal window
# CI/CD pipeline integration
setup_ci_integration() {
local repo_path="$1"
local ci_system="$2"
echo "Setting up CI/CD integration for $ci_system"
cd "$repo_path" || exit 1
# Configure for CI system
case "$ci_system" in
"github-actions")
cat > hooks/post-receive << 'EOF'
#!/bin/bash
# Trigger GitHub Actions
while read oldrev newrev refname; do
if [[ "$refname" == "refs/heads/main" ]]; then
echo "Triggering GitHub Actions workflow..."
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/ci.yml/dispatches" \
-d '{"ref": "main"}'
fi
done
EOF
;;
"gitlab-ci")
cat > hooks/post-receive << 'EOF'
#!/bin/bash
# Trigger GitLab CI
while read oldrev newrev refname; do
if [[ "$refname" == "refs/heads/main" ]]; then
echo "Triggering GitLab CI pipeline..."
curl -X POST \
-H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/trigger/pipeline"
fi
done
EOF
;;
"jenkins")
cat > hooks/post-receive << 'EOF'
#!/bin/bash
# Trigger Jenkins build
while read oldrev newrev refname; do
if [[ "$refname" == "refs/heads/main" ]]; then
echo "Triggering Jenkins build..."
curl -X POST "$JENKINS_URL/job/my-project/build"
fi
done
EOF
;;
esac
chmod +x hooks/post-receive
echo "CI/CD integration setup complete"
}
setup_ci_integration "/srv/git/myrepo.git" "github-actions"
Terminal window
# Advanced access control with auditing
setup_access_control() {
local repo_path="$1"
echo "Setting up advanced access control"
cd "$repo_path" || exit 1
# Create audit log
mkdir -p audit
# Enhanced pre-receive hook
cat > hooks/pre-receive << 'EOF'
#!/bin/bash
# Advanced access control and auditing
LOG_FILE="audit/push-log.txt"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
USER=${USER:-$GIT_AUTHOR_NAME}
IP=${SSH_CLIENT%% *}
echo "$TIMESTAMP|$USER|$IP|START" >> "$LOG_FILE"
# Read all refs being pushed
while read oldrev newrev refname; do
echo "$TIMESTAMP|$USER|$IP|PUSH|$refname|$oldrev|$newrev" >> "$LOG_FILE"
# Access control logic
case "$refname" in
refs/heads/main)
# Strict rules for main branch
if ! user_has_main_access "$USER"; then
echo "Access denied to main branch"
exit 1
fi
# Require code review
if ! commit_has_review "$newrev"; then
echo "Main branch requires code review"
exit 1
fi
;;
refs/heads/release/*)
# Release branch rules
if ! user_has_release_access "$USER"; then
echo "Access denied to release branches"
exit 1
fi
;;
refs/tags/*)
# Tag creation rules
if ! user_has_tag_access "$USER"; then
echo "Access denied for tag creation"
exit 1
fi
;;
esac
# Size limits
objects=$(git rev-list --objects "$oldrev..$newrev")
if [ $(echo "$objects" | wc -l) -gt 1000 ]; then
echo "Push too large - please split into smaller pushes"
exit 1
fi
done
echo "$TIMESTAMP|$USER|$IP|SUCCESS" >> "$LOG_FILE"
EOF
# Helper functions
cat > hooks/access-control.sh << 'EOF'
#!/bin/bash
# Access control helper functions
user_has_main_access() {
local user="$1"
# Check against main branch access list
grep -q "^$user$" /etc/git/main-access.txt
}
user_has_release_access() {
local user="$1"
# Check against release access list
grep -q "^$user$" /etc/git/release-access.txt
}
user_has_tag_access() {
local user="$1"
# Check against tag access list
grep -q "^$user$" /etc/git/tag-access.txt
}
commit_has_review() {
local commit="$1"
# Check for review markers in commit message
git show -s --format=%B "$commit" | grep -q "Reviewed-by:"
}
EOF
chmod +x hooks/pre-receive
chmod +x hooks/access-control.sh
echo "Advanced access control setup complete"
}
setup_access_control "/srv/git/secure-repo.git"
Terminal window
# Disaster recovery with receive-pack
setup_disaster_recovery() {
local primary_repo="$1"
local backup_repo="$2"
echo "Setting up disaster recovery"
# Configure backup hooks
cd "$primary_repo" || exit 1
cat > hooks/post-receive << EOF
#!/bin/bash
# Automatic backup to secondary repository
while read oldrev newrev refname; do
echo "Backing up \$refname to $backup_repo"
# Push to backup repository
git push --mirror "$backup_repo" "\$refname"
# Log backup
echo "\$(date): Backed up \$refname \$oldrev -> \$newrev" >> backup.log
done
EOF
chmod +x hooks/post-receive
# Set up backup repository
git clone --mirror "$primary_repo" "$backup_repo"
# Configure backup repository
cd "$backup_repo" || exit 1
git config receive.denyNonFastForwards false
git config receive.denyDeletes false
echo "Disaster recovery setup complete"
}
setup_disaster_recovery "/srv/git/primary.git" "/srv/git/backup.git"
Terminal window
# Implement custom push policies
setup_push_policies() {
local repo_path="$1"
echo "Setting up custom push policies"
cd "$repo_path" || exit 1
# Create policy configuration
cat > push-policies.conf << EOF
# Push policy configuration
[branches]
main = protected,require-review,require-tests
develop = require-tests
feature/* = allow-all
[tags]
v* = require-gpg,require-review
rc* = require-tests
[general]
max-commit-size = 1000
require-signoff = true
allow-force-push = false
EOF
# Policy enforcement hook
cat > hooks/pre-receive << 'EOF'
#!/bin/bash
# Enforce push policies
source ./push-policies.conf
while read oldrev newrev refname; do
echo "Checking policies for $refname"
# Determine branch/tag type
if [[ "$refname" =~ ^refs/heads/ ]]; then
branch="${refname#refs/heads/}"
policy=$(get_branch_policy "$branch")
elif [[ "$refname" =~ ^refs/tags/ ]]; then
tag="${refname#refs/tags/}"
policy=$(get_tag_policy "$tag")
fi
# Apply policies
IFS=',' read -ra POLICIES <<< "$policy"
for policy_item in "${POLICIES[@]}"; do
case "$policy_item" in
protected)
if ! is_fast_forward "$oldrev" "$newrev"; then
echo "Protected branch requires fast-forward"
exit 1
fi
;;
require-review)
if ! has_review_approval "$newrev"; then
echo "Branch requires review approval"
exit 1
fi
;;
require-tests)
if ! tests_pass "$newrev"; then
echo "Tests must pass before push"
exit 1
fi
;;
require-gpg)
if ! commits_signed "$oldrev" "$newrev"; then
echo "All commits must be GPG signed"
exit 1
fi
;;
esac
done
# General policies
if [ "$require_signoff" = "true" ] && ! commits_have_signoff "$oldrev" "$newrev"; then
echo "Commits must have signoff"
exit 1
fi
commit_count=$(git rev-list --count "$oldrev..$newrev")
if [ "$commit_count" -gt "$max_commit_size" ]; then
echo "Push too large (max $max_commit_size commits)"
exit 1
fi
done
echo "All push policies satisfied"
EOF
chmod +x hooks/pre-receive
echo "Custom push policies setup complete"
}
setup_push_policies "/srv/git/policy-repo.git"

What’s the difference between receive-pack and send-pack?

Section titled “What’s the difference between receive-pack and send-pack?”

receive-pack runs on the server side to receive pushed data; send-pack runs on the client side to send push data. They work together to implement the Git push protocol.

How do I configure receive-pack for a bare repository?

Section titled “How do I configure receive-pack for a bare repository?”

Use git init —bare, then configure receive.* settings in the config file. Set up hooks in the hooks/ directory for access control and automation.

What are the security implications of receive-pack?

Section titled “What are the security implications of receive-pack?”

receive-pack can modify repository references, so it should be protected with proper authentication, authorization hooks, and input validation to prevent malicious pushes.

Use GIT_TRACE=1 git push to see protocol traces, check server logs, and examine hook outputs. Test hooks manually with sample input.

Yes, it’s used by git-http-backend for HTTP push operations. Requires proper web server configuration and authentication.

What’s the difference between pre-receive and post-receive hooks?

Section titled “What’s the difference between pre-receive and post-receive hooks?”

pre-receive runs before references are updated (can reject push); post-receive runs after successful updates (for notifications and automation).

How do I limit push size with receive-pack?

Section titled “How do I limit push size with receive-pack?”

Configure receive.maxInputSize and receive.unpackLimit in Git config, or implement size checks in pre-receive hooks.

Yes, but this can be controlled with receive.denyNonFastForwards and receive.denyDeletes configurations.

How do I set up receive-pack for SSH access?

Section titled “How do I set up receive-pack for SSH access?”

Configure SSH authorized_keys with command=“git-receive-pack ‘/path/to/repo.git’” to restrict users to Git operations only.

What’s the role of receive-pack in CI/CD?

Section titled “What’s the role of receive-pack in CI/CD?”

receive-pack triggers post-receive hooks that can start CI/CD pipelines, deploy code, or send notifications when pushes occur.

How do I backup repositories using receive-pack?

Section titled “How do I backup repositories using receive-pack?”

Set up mirror repositories and use post-receive hooks to automatically push to backup locations after successful receives.

Applications of the git receive-pack command

Section titled “Applications of the git receive-pack command”
  1. Server-Side Push Processing: Handle incoming push operations and update repositories
  2. Access Control Implementation: Enforce push policies and security restrictions
  3. Hook-Based Automation: Trigger CI/CD, deployments, and notifications on push
  4. Repository Mirroring: Maintain backup and mirror repositories
  5. Protocol Implementation: Support SSH and HTTP Git push protocols
  6. Enterprise Git Management: Implement organizational policies and compliance