Skip to content

protocol-v2 Git Command Guide

The Git protocol version 2 is the modern stateless protocol that improves upon version 1 with better performance, cleaner architecture, and enhanced capabilities. It separates concerns and enables more efficient Git operations across all transports.

Protocol V2 Characteristics:
├── Stateless: No server-side state between requests
├── Command-based: Explicit command structure
├── Capability-driven: Feature negotiation
├── Transport-agnostic: Works over any transport
└── Extensible: Easy to add new commands/features
Advantages over V1:
├── Better performance for large repositories
├── Cleaner separation of concerns
├── Improved error handling
├── Enhanced security features
└── Future extensibility
V2 Protocol Flow:
1. Client → Server: command request
2. Server → Client: capability advertisement
3. Client → Server: command parameters
4. Server → Client: command response
5. Connection closes (stateless)
Essential V2 Commands:
├── ls-refs: List and filter references
├── fetch: Fetch objects and references
├── push: Send objects and update references
├── clone: Initial repository cloning
└── bundle-uri: CDN-based content delivery
ls-refs Capabilities:
├── symrefs: Symbolic reference support
├── ref-in-want: References in want lines
├── allow-tip-sha1-in-want: SHA-1 in want lines
├── allow-reachable-sha1-in-want: Reachable SHA-1 in want
└── ref-prefix: Reference prefix filtering
fetch Capabilities:
├── multi_ack_detailed: Detailed ACK responses
├── no-done: Skip 'done' packet
├── thin-pack: Thin pack support
├── side-band: Progress reporting
├── side-band-64k: 64KB side-band
├── ofs-delta: Offset delta support
├── shallow: Shallow fetch support
├── deepen-since: Time-based deepening
├── deepen-not: Reference-based deepening
├── deepen-relative: Relative deepening
├── filter: Object filtering
└── bundle-uri: Bundle URI support
push Capabilities:
├── report-status: Operation status reporting
├── report-status-v2: Enhanced status reporting
├── delete-refs: Reference deletion
├── atomic: Atomic reference updates
├── object-format: Object format specification
├── allow-tip-sha1-in-refspec: SHA-1 in refspecs
└── allow-reachable-sha1-in-refspec: Reachable SHA-1 in refspecs
Extended Capabilities:
├── server-option: Server option support
├── object-info: Object information queries
├── session-id: Session identification
├── bundle-uri: Bundle URI support
├── packfile-uris: Pack file URI support
└── wait-for-done: Asynchronous operation support
Terminal window
# List references with filtering
Client Request:
command=ls-refs
object-format=sha1
ref-prefix refs/heads/
ref-prefix refs/tags/
Server Response:
<reference-advertisement>
<symref-advertisement>
<capability-advertisement>
Terminal window
# Fetch with filtering and optimization
Client Request:
command=fetch
object-format=sha1
want <sha1>
want <sha1>
have <sha1>
filter blob:limit=1m
done
Server Response:
<acknowledgments>
<packfile-data>
Terminal window
# Push with status reporting
Client Request:
command=push
object-format=sha1
oldref <ref> <sha1>
newref <ref> <sha1>
Server Response:
<status-reports>
<command-status>
Terminal window
# Force protocol version 2
git config --global protocol.version 2
# Per-remote configuration
git config remote.origin.protocol version=2
# Environment variable
export GIT_PROTOCOL=version=2
Terminal window
# Enable V2 on server
git config --global uploadpack.allowFilter true
git config --global uploadpack.allowAnySHA1InWant true
git config --global receive.advertisePushOptions true
# Configure capabilities
git config --global uploadpack.allowRefInWant true
git config --global uploadpack.allowTipSHA1InWant true
Terminal window
# Configure V2 preferences
git config --global fetch.writeCommitGraph true
git config --global fetch.bundleURI true
# Enable filtering
git config --global fetch.negotiateAlgorithm skipping
# Configure push options
git config --global push.useForceIfIncludes true
Terminal window
# Enable commit graph for faster traversal
git commit-graph write --reachable
# Use bundle URIs for faster clones
# Server advertises bundle URIs for CDN delivery
# Enable partial clones
git clone --filter=blob:limit=1m <url>
# Use shallow clones
git clone --depth=1 <url>
Terminal window
# Use atomic pushes
git push --atomic origin main feature
# Enable force-with-lease
git push --force-with-lease origin main
# Use push options
git push -o ci.skip origin main
Terminal window
# Enable protocol V2
git config protocol.version 2
# Configure connection reuse
git config http.maxRequests 100
# Enable compression
git config core.compression 9
Terminal window
# Blob size filtering
git clone --filter=blob:limit=1m <url>
# Tree filtering
git clone --filter=tree:depth=1 <url>
# Combined filtering
git clone --filter=combine:blob:limit=1m+tree:depth=1 <url>
Terminal window
# Server advertises bundle locations
# Client can download from CDNs
# Configure bundle URI
git config --global fetch.bundleURI true
# Manual bundle usage
git bundle create my.bundle --all
git clone my.bundle
Terminal window
# Wait-for-done capability
# Server can process operations asynchronously
# Client requests async operation
command=fetch
wait-for-done
# Server acknowledges and processes
# Client can disconnect and reconnect later
Terminal window
# Enable protocol tracing
GIT_TRACE=1 git fetch origin
# Trace packet exchange
GIT_TRACE_PACKET=1 git clone <url>
# Debug capability negotiation
GIT_TRACE=2 git push origin main
Terminal window
# Check server capabilities
git ls-remote --upload-pack="git upload-pack --advertise-capabilities" <url>
# Test V2 support
curl -s "https://github.com/user/repo.git/info/refs?service=git-upload-pack" | head -10
# Force V2 usage
GIT_PROTOCOL=version=2 git clone <url>
Terminal window
# Monitor V2 performance
time git clone --depth=1 <url>
# Check protocol version
git config protocol.version
# Analyze fetch performance
GIT_TRACE_PERFORMANCE=1 git fetch origin
#!/bin/bash
# Basic Git protocol V2 server
handle_v2_request() {
local service="$1"
local repo_path="$2"
# Read client request
read -r line
# Parse command
if [[ "$line" == "command="* ]]; then
command="${line#command=}"
case "$command" in
"ls-refs")
handle_ls_refs "$repo_path"
;;
"fetch")
handle_fetch "$repo_path"
;;
"push")
handle_push "$repo_path"
;;
*)
echo "ERR unknown command '$command'"
;;
esac
else
echo "ERR invalid request format"
fi
}
handle_ls_refs() {
local repo="$1"
# Advertise capabilities
echo "version 2"
echo "capability-advertisement"
echo "ls-refs"
echo "fetch"
echo "server-option"
echo ""
# Send reference list
git -C "$repo" for-each-ref --format="%(refname) %(objectname)"
}
handle_fetch() {
local repo="$1"
# Read fetch parameters
while read -r line; do
case "$line" in
"want "*)
wants+=("${line#want }")
;;
"have "*)
haves+=("${line#have }")
;;
"done")
break
;;
esac
done
# Perform fetch negotiation
# Send pack data
git -C "$repo" upload-pack --stateless-rpc
}
# Usage
handle_v2_request "upload-pack" "/path/to/repo.git"
Terminal window
# Server with filtering support
handle_fetch_with_filter() {
local repo="$1"
# Parse filter options
while read -r line; do
case "$line" in
"filter "*)
filter="${line#filter }"
;;
"want "*)
# Apply filter to wanted objects
;;
esac
done
# Generate filtered pack
case "$filter" in
"blob:limit="*)
limit="${filter#blob:limit=}"
# Generate pack with blob size filtering
;;
"tree:depth="*)
depth="${filter#tree:depth=}"
# Generate pack with tree depth filtering
;;
esac
}
# Server with bundle URI support
advertise_bundle_uris() {
echo "bundle-uri=https://cdn.example.com/bundle.bundle"
echo "bundle-uri=https://backup.example.com/bundle.bundle"
}
# Server with session tracking
track_sessions() {
local session_id="$1"
# Log session activity
echo "$(date): Session $session_id - $command" >> session.log
# Implement session-based optimizations
# Cache results, track user patterns, etc.
}
Terminal window
# Gradual migration strategy
migration_strategy() {
echo "Migrating from Git protocol V1 to V2"
# Phase 1: Enable V2 alongside V1
git config --global protocol.version 2
# Phase 2: Test V2 compatibility
test_v2_compatibility
# Phase 3: Monitor performance improvements
monitor_v2_performance
# Phase 4: Full migration
enforce_v2_usage
}
test_v2_compatibility() {
# Test basic operations
git ls-remote <url>
git fetch origin
git push origin main
# Check for V2-specific features
git clone --filter=blob:limit=1m <url>
}
monitor_v2_performance() {
# Compare V1 vs V2 performance
echo "Testing V1 performance:"
time GIT_PROTOCOL=version=1 git clone --depth=1 <url> v1-repo
echo "Testing V2 performance:"
time GIT_PROTOCOL=version=2 git clone --depth=1 <url> v2-repo
}
enforce_v2_usage() {
# Force V2 usage
git config --global protocol.version 2
# Update server configurations
# Disable V1 support if desired
}
Terminal window
# Handle mixed client versions
server_compatibility_handler() {
# Detect client protocol version
if client_supports_v2; then
use_protocol_v2
else
fallback_to_v1
fi
}
client_supports_v2() {
# Test V2 support
git ls-remote --upload-pack="git upload-pack --advertise-capabilities" <url> | grep -q "version 2"
}
# Backward compatibility
legacy_v1_support() {
# Maintain V1 support for older clients
git config uploadpack.allowFilter false
git config uploadpack.allowAnySHA1InWant false
}
Terminal window
# V2-specific security features
v2_security_features() {
# Reference-based access control
# Object filtering for security
# Session-based authentication
# Audit logging improvements
}
# Implement V2-aware access control
check_v2_permissions() {
local command="$1"
local user="$2"
local repo="$3"
case "$command" in
"ls-refs")
# Check read permissions
;;
"fetch")
# Check fetch permissions
;;
"push")
# Check push permissions
;;
esac
}
Terminal window
# Enhanced V2 audit logging
v2_audit_logging() {
local session_id="$1"
local command="$2"
local user="$3"
# Log V2-specific information
echo "$(date): V2 $session_id $user $command" >> audit.log
# Track capabilities used
# Monitor performance metrics
# Log security events
}
Terminal window
# V2 not supported by server
GIT_PROTOCOL=version=1 git clone <url>
# Capability negotiation failures
git config protocol.version 1 # Temporary fallback
# Filter not supported
git clone --no-filter <url>
# Bundle URI issues
git config fetch.bundleURI false
Terminal window
# Enable detailed tracing
GIT_TRACE=2 GIT_TRACE_PACKET=1 git fetch origin
# Check server V2 support
curl -s "https://github.com/user/repo.git/info/refs?service=git-upload-pack" | grep "version 2"
# Test V2 commands manually
echo -e "command=ls-refs\nobject-format=sha1\n" | git upload-pack --stateless-rpc <url>
Terminal window
# V2 slower than expected
GIT_PROTOCOL=version=1 git clone <url> # Compare
# Disable V2 features for troubleshooting
git config protocol.version 1
# Check network issues
ping github.com
traceroute github.com
#!/bin/bash
# Enterprise Git protocol V2 deployment
enterprise_v2_deployment() {
local git_server="$1"
echo "Deploying Git protocol V2 on $git_server"
# Configure server for V2
ssh "$git_server" << 'EOF'
# Enable V2 capabilities
git config --global uploadpack.allowFilter true
git config --global uploadpack.allowAnySHA1InWant true
git config --global receive.advertisePushOptions true
# Configure V2-specific settings
git config --global uploadpack.allowRefInWant true
git config --global uploadpack.allowTipSHA1InWant true
# Enable bundle URIs for performance
git config --global uploadpack.bundleURI true
# Set up monitoring
git config --global uploadpack.statelessRPC true
EOF
# Test V2 functionality
test_v2_server "$git_server"
# Configure clients
configure_v2_clients
# Set up monitoring
setup_v2_monitoring "$git_server"
}
test_v2_server() {
local server="$1"
echo "Testing V2 server functionality..."
# Test basic V2 operations
git ls-remote "git@$server:test-repo.git"
# Test filtering
git clone --filter=blob:limit=1m "git@$server:test-repo.git" filtered-clone
# Test bundle URIs
git clone --bundle-uri "git@$server:test-repo.git" bundle-clone
echo "V2 server tests completed"
}
configure_v2_clients() {
echo "Configuring clients for V2..."
# Global V2 configuration
git config --global protocol.version 2
git config --global fetch.writeCommitGraph true
git config --global fetch.bundleURI true
# Configure filtering preferences
git config --global fetch.negotiateAlgorithm skipping
echo "Client configuration completed"
}
setup_v2_monitoring() {
local server="$1"
echo "Setting up V2 monitoring..."
# Server-side monitoring
ssh "$server" << 'EOF'
# Install monitoring script
cat > /opt/git-monitor.sh << 'MONITOR_EOF'
#!/bin/bash
# Git V2 monitoring script
while true; do
# Count active V2 connections
v2_connections=$(netstat -tn | grep :9418 | wc -l)
# Log V2 usage
echo "$(date): V2 connections: $v2_connections" >> /var/log/git-v2.log
# Monitor performance
git_processes=$(pgrep -f "git.*upload-pack" | wc -l)
echo "$(date): Git processes: $git_processes" >> /var/log/git-v2.log
sleep 60
done
MONITOR_EOF
chmod +x /opt/git-monitor.sh
EOF
echo "V2 monitoring setup completed"
}
enterprise_v2_deployment "git.company.com"
Terminal window
# Optimize CI/CD pipelines with V2
ci_v2_optimization() {
echo "Optimizing CI/CD for Git protocol V2..."
# Configure V2 for CI
git config --global protocol.version 2
# Enable shallow clones for faster builds
export GIT_SHALLOW=true
# Configure fetch optimizations
git config --global fetch.prune true
git config --global fetch.writeCommitGraph true
git config --global fetch.bundleURI true
# Use partial clones for large repos
git clone --filter=blob:limit=1m <url> repo
# Optimize for CI patterns
git config --global gc.autoDetach false
git config --global feature.experimental true
# Configure push optimizations
git config --global push.useForceIfIncludes true
git config --global push.negotiate true
echo "CI/CD V2 optimization completed"
}
ci_v2_optimization
#!/bin/bash
# Complete Git protocol V2 server implementation
GIT_V2_SERVER_VERSION="2.0"
v2_server() {
local repo_root="$1"
local port="${2:-9418}"
echo "Git Protocol V2 Server v$GIT_V2_SERVER_VERSION"
echo "Repository root: $repo_root"
echo "Listening on port: $port"
# Main server loop
while true; do
# Accept connection (simplified - use proper socket handling in production)
read -r line
# Parse initial request
if [[ "$line" == "git-upload-pack "* ]]; then
service="upload-pack"
repo="${line#git-upload-pack }"
elif [[ "$line" == "git-receive-pack "* ]]; then
service="receive-pack"
repo="${line#git-receive-pack }"
else
send_error "ERR Invalid service request"
continue
fi
# Validate repository
repo_path="$repo_root$repo"
if [ ! -d "$repo_path" ]; then
send_error "ERR Repository not found"
continue
fi
# Handle V2 protocol
handle_v2_protocol "$service" "$repo_path"
done
}
handle_v2_protocol() {
local service="$1"
local repo_path="$2"
# Read command request
read -r command_line
if [[ "$command_line" != "command="* ]]; then
# Fallback to V1 protocol
handle_v1_fallback "$service" "$repo_path"
return
fi
command="${command_line#command=}"
# Send capability advertisement
send_capabilities "$command"
# Handle command
case "$command" in
"ls-refs")
handle_ls_refs "$repo_path"
;;
"fetch")
handle_fetch "$repo_path"
;;
"push")
handle_push "$repo_path"
;;
*)
send_error "ERR Unknown command: $command"
;;
esac
}
send_capabilities() {
local command="$1"
echo "version 2"
echo "capability-advertisement"
# Advertise supported capabilities based on command
case "$command" in
"ls-refs")
echo "ls-refs"
echo "symrefs"
echo "ref-in-want"
;;
"fetch")
echo "fetch"
echo "filter"
echo "side-band"
echo "thin-pack"
;;
"push")
echo "push"
echo "report-status"
echo "atomic"
;;
esac
echo "server-option"
echo "object-format=sha1"
echo "session-id=$(uuidgen)"
echo ""
}
handle_ls_refs() {
local repo_path="$1"
# Parse client parameters
while read -r line; do
case "$line" in
"symrefs")
include_symrefs=true
;;
"ref-prefix "*)
prefixes+=("${line#ref-prefix }")
;;
"")
break
;;
esac
done
# Send reference list
git -C "$repo_path" for-each-ref --format="%(refname) %(objectname)" "${prefixes[@]}"
# Send symrefs if requested
if [ "$include_symrefs" = true ]; then
head_ref=$(git -C "$repo_path" symbolic-ref HEAD 2>/dev/null)
if [ -n "$head_ref" ]; then
echo "symref HEAD $head_ref"
fi
fi
echo "" # End of response
}
handle_fetch() {
local repo_path="$1"
# Parse fetch parameters
declare -a wants haves
local filter=""
while read -r line; do
case "$line" in
"want "*)
wants+=("${line#want }")
;;
"have "*)
haves+=("${line#have }")
;;
"filter "*)
filter="${line#filter }"
;;
"done")
break
;;
esac
done
# Perform fetch negotiation
# Send acknowledgments
for have in "${haves[@]}"; do
if git -C "$repo_path" cat-file -e "$have" 2>/dev/null; then
echo "ACK $have common"
fi
done
# Send pack data
{
echo "packfile"
# Generate and send pack file
git -C "$repo_path" pack-objects --stdout --thin --delta-base-offset "${wants[@]}"
}
}
handle_push() {
local repo_path="$1"
# Parse push parameters
declare -a updates
while read -r line; do
case "$line" in
"oldref "*)
# Parse oldref updates
;;
"newref "*)
# Parse newref updates
;;
"")
break
;;
esac
done
# Perform push operation
# Update references
# Send status report
echo "ok refs/heads/main"
echo ""
}
handle_v1_fallback() {
local service="$1"
local repo_path="$2"
# Fallback to V1 protocol handling
case "$service" in
"upload-pack")
git -C "$repo_path" upload-pack
;;
"receive-pack")
git -C "$repo_path" receive-pack
;;
esac
}
send_error() {
echo "$1"
echo ""
}
# Usage
v2_server "/var/git" 9418

What’s the difference between Git protocol V1 and V2?

Section titled “What’s the difference between Git protocol V1 and V2?”

V1 uses stateful connections with mixed command/data streams; V2 uses stateless connections with clear command structure, better performance, and cleaner architecture.

Set git config protocol.version 2 globally or per-remote. Most modern Git clients and servers support V2.

What are the main advantages of V2 over V1?

Section titled “What are the main advantages of V2 over V1?”

Stateless design, better performance for large repos, cleaner command structure, enhanced filtering capabilities, and improved extensibility.

V2 clients can fall back to V1 when servers don’t support V2. Use GIT_PROTOCOL=version=1 to force V1 if needed.

Replaces reference advertisement in V1 with a dedicated command for listing and filtering references, enabling more efficient and flexible ref discovery.

Stateless design reduces server load, filtering allows partial clones, and better negotiation algorithms optimize data transfer.

Allows clients to request only specific objects (e.g., blobs under a size limit), enabling partial clones and reducing bandwidth for large repositories.

Servers can advertise pre-computed bundle locations (CDNs, caches) for faster initial clones, offloading server processing.

Provides unique session identification for tracking and debugging protocol interactions across multiple requests.

Yes, V2 works over HTTP/HTTPS with the same benefits. HTTP servers need to support V2 endpoints and capabilities.

Use GIT_TRACE=1 and GIT_TRACE_PACKET=1, check server capability advertisements, and test with GIT_PROTOCOL=version=1 for comparison.

What happens if a V2 client connects to a V1 server?

Section titled “What happens if a V2 client connects to a V1 server?”

Client detects lack of V2 support and falls back to V1 protocol automatically.

Are there any V2 features that require special server configuration?

Section titled “Are there any V2 features that require special server configuration?”

Yes, filtering requires uploadpack.allowFilter=true, ref-in-want requires uploadpack.allowRefInWant=true, etc.

Same as V1 - authentication is transport-specific (SSH keys, HTTP tokens, etc.), not protocol-specific.

What’s the performance impact of enabling V2?

Section titled “What’s the performance impact of enabling V2?”

Generally better performance, especially for large repositories and complex operations. Some overhead for small, simple operations.

Yes, servers can be configured to only support V1, though this is not recommended for modern deployments.

Check Git server logs for V2 command usage, monitor session-ids, and track capability advertisements.

What’s the future of Git protocol development?

Section titled “What’s the future of Git protocol development?”

V2 is the current standard with ongoing improvements. Future versions may add more advanced filtering, better compression, and enhanced security features.

  1. High-Performance Git Operations: Enable faster clones, fetches, and pushes for large repositories
  2. Partial Repository Cloning: Support filtered clones for monorepos and large codebases
  3. CDN-Accelerated Downloads: Use bundle URIs for faster initial repository access
  4. Enterprise Security: Implement advanced access controls and audit capabilities
  5. CI/CD Optimization: Improve build performance with shallow and filtered clones
  6. Scalable Git Hosting: Support millions of repositories with efficient protocol design