Skip to content

refs Git Command Guide

The git for-each-ref command iterates over all refs that match specified patterns and displays them according to custom formats, with powerful sorting and filtering capabilities.

Terminal window
git for-each-ref [--count=<count>] [--shell|--perl|--python|--tcl]
[(--sort=<key>)...] [--format=<format>]
[--include-root-refs] [--points-at=<object>]
[--merged[=<object>]] [--no-merged[=<object>]]
[--contains[=<object>]] [--no-contains[=<object>]]
[(--exclude=<pattern>)...] [--start-after=<marker>]
[ --stdin | <pattern>... ]
OptionDescription
--count=<count>Limit number of refs shown
`—shell—perl
--format=<format>Custom output format
OptionDescription
--sort=<key>Sort by specified key (multiple allowed)
--start-after=<marker>Start after specified ref
OptionDescription
--points-at=<object>Show refs pointing to object
--merged[=<object>]Show refs merged into object
--no-merged[=<object>]Show refs not merged into object
--contains[=<object>]Show refs containing object
--no-contains[=<object>]Show refs not containing object
--exclude=<pattern>Exclude refs matching pattern
--include-root-refsInclude root refs
ParameterDescription
<pattern>Ref patterns to match (fnmatch or literal)
--stdinRead patterns from stdin
Git Reference Hierarchy:
├── Branches: refs/heads/ (local branches)
├── Remotes: refs/remotes/ (remote tracking branches)
├── Tags: refs/tags/ (annotated and lightweight tags)
├── Notes: refs/notes/ (note objects)
├── Stash: refs/stash (stash entries)
└── Special: HEAD, ORIG_HEAD, MERGE_HEAD, etc.
Reference Storage:
├── Stored in .git/refs/ directory hierarchy
├── Packed refs in .git/packed-refs file
├── Symbolic refs point to other refs
└── Reflogs track reference changes
Common Format Fields:
├── %(refname) # Full ref name
├── %(refname:short) # Short ref name
├── %(objectname) # Object SHA-1
├── %(objectname:short) # Short object SHA-1
├── %(authorname) # Author name
├── %(authoremail) # Author email
├── %(authordate) # Author date
├── %(committername) # Committer name
├── %(committerdate) # Committer date
├── %(subject) # Commit subject
├── %(body) # Commit body
├── %(upstream) # Upstream branch
├── %(upstream:short) # Short upstream name
└── %(push) # Push destination
Advanced Fields:
├── %(HEAD) # * if HEAD, else space
├── %(color:<color>) # Color output
├── %(align:<width>,<position>) # Text alignment
├── %(if:<condition>)%<(then) # Conditional output
└── %(symref) # Symbolic ref target
Available Sort Keys:
├── refname # Lexical sort by ref name
├── objectname # Sort by object SHA-1
├── objectsize # Sort by object size
├── authordate # Sort by author date
├── commitdate # Sort by commit date
├── version:refname # Version sort for tags
├── -key # Reverse sort
└── Multiple keys: --sort=key1 --sort=key2
Terminal window
# List all refs with default format
git for-each-ref
# List refs with short format
git for-each-ref --format='%(refname:short)'
# List refs with object info
git for-each-ref --format='%(refname) %(objectname) %(subject)'
Terminal window
# List all branches
git for-each-ref refs/heads/
# List all remote branches
git for-each-ref refs/remotes/
# List all tags
git for-each-ref refs/tags/
# List specific remote
git for-each-ref refs/remotes/origin/
Terminal window
# Branch status format
git for-each-ref --format='%(HEAD) %(refname:short) %(upstream:short)' refs/heads/
# Tag information
git for-each-ref --format='%(refname:short) %(authordate) %(subject)' refs/tags/
# Detailed commit info
git for-each-ref --format='%(refname:short): %(authorname) <%(authoremail)> %(committerdate:relative)' refs/heads/
Terminal window
# Sort by author date (newest first)
git for-each-ref --sort=-authordate --format='%(refname:short) %(authordate:relative)' refs/heads/
# Sort by version for tags
git for-each-ref --sort=version:refname --format='%(refname:short)' refs/tags/
# Limit output
git for-each-ref --count=10 --sort=-committerdate refs/heads/
Terminal window
# Branches containing specific commit
git for-each-ref --contains=<commit> --format='%(refname:short)' refs/heads/
# Branches not containing commit
git for-each-ref --no-contains=<commit> --format='%(refname:short)' refs/heads/
# Branches merged into main
git for-each-ref --merged=main --format='%(refname:short)' refs/heads/
# Branches not merged into main
git for-each-ref --no-merged=main --format='%(refname:short)' refs/heads/
Terminal window
# Match specific patterns
git for-each-ref 'refs/heads/feature/*'
# Exclude patterns
git for-each-ref --exclude='refs/heads/main' --exclude='refs/heads/develop' refs/heads/
# Complex patterns
git for-each-ref 'refs/heads/*' 'refs/tags/v1.*'
Terminal window
# Refs pointing to specific commit
git for-each-ref --points-at=<commit> --format='%(refname)'
# Branches pointing to HEAD
git for-each-ref --points-at=HEAD refs/heads/
# Tags pointing to specific object
git for-each-ref --points-at=<object> refs/tags/
Terminal window
# Configure default formats
git config pretty.for-each-ref "%(refname:short) %(subject)"
# Configure color output
git config color.for-each-ref true
# Configure sorting preferences
git config for-each-ref.sort -committerdate
Terminal window
# Always test formats
git for-each-ref --format='%(refname)' | head -5
# Use count limits for large repos
git for-each-ref --count=50 refs/heads/
# Validate patterns
git for-each-ref 'refs/heads/*' 2>/dev/null || echo "Invalid pattern"
# Handle empty results
if git for-each-ref --format='%(refname)' refs/heads/ | grep -q .; then
echo "Branches found"
else
echo "No branches found"
fi
Terminal window
# Use specific patterns to limit scope
git for-each-ref refs/heads/feature/ # Faster than refs/heads/
# Avoid expensive fields in large repos
# Use %(refname:short) instead of %(subject) for speed
# Cache results when possible
REFS_CACHE=$(git for-each-ref --format='%(refname)' refs/heads/)
#!/bin/bash
# Branch management dashboard
branch_dashboard() {
echo "=== Branch Dashboard ==="
echo ""
# Active branches with status
echo "Active Branches:"
git for-each-ref --sort=-committerdate --format='%(HEAD)%(color:green)%(refname:short)%(color:reset) %(color:blue)%(upstream:short)%(color:reset) %(authordate:relative) %(subject)' refs/heads/ | head -10
echo ""
# Stale branches
echo "Potentially Stale Branches (>90 days):"
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:relative)' refs/heads/ | awk '$2 ~ /weeks?|months?|years?/ {print}' | head -5
echo ""
# Branch statistics
total_branches=$(git for-each-ref refs/heads/ | wc -l)
merged_branches=$(git for-each-ref --merged=HEAD refs/heads/ | grep -v HEAD | wc -l)
echo "Statistics: $total_branches total branches, $merged_branches merged into HEAD"
}
branch_dashboard
Terminal window
# Tag management with for-each-ref
tag_management() {
echo "=== Tag Management ==="
# Recent tags
echo "Recent Tags:"
git for-each-ref --sort=-version:refname --format='%(refname:short) %(authordate:relative) %(subject)' refs/tags/ | head -10
echo ""
# Annotated vs lightweight
echo "Tag Types:"
echo "Annotated tags:"
git for-each-ref --format='%(refname:short)' refs/tags/ | while read -r tag; do
if git cat-file -t "$tag" 2>/dev/null | grep -q tag; then
echo " $tag"
fi
done
echo "Lightweight tags:"
git for-each-ref --format='%(refname:short)' refs/tags/ | while read -r tag; do
if ! git cat-file -t "$tag" 2>/dev/null | grep -q tag; then
echo " $tag"
fi
done
}
tag_management
Terminal window
# Repository health check using refs
health_check() {
echo "=== Repository Health Check ==="
# Check for broken refs
echo "Checking for broken refs..."
git for-each-ref --format='%(refname)' | while read -r ref; do
if ! git rev-parse --verify "$ref" >/dev/null 2>&1; then
echo "Broken ref: $ref"
fi
done
# Check reflog sizes
echo "Reflog sizes:"
git for-each-ref --format='%(refname)' | while read -r ref; do
reflog_size=$(git reflog --format='%H' "$ref" 2>/dev/null | wc -l)
if [ "$reflog_size" -gt 1000 ]; then
echo "Large reflog: $ref ($reflog_size entries)"
fi
done
# Check for old branches
echo "Old branches (>6 months):"
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:relative)' refs/heads/ | awk '$2 ~ /months?|years?/ {print}' | head -5
echo "Health check complete"
}
health_check
Terminal window
# Debug format strings
git for-each-ref --format='%(refname)' 2>&1 | head -5
# Escape special characters
git for-each-ref --format='Branch: %(refname:short)' refs/heads/
# Handle missing fields
git for-each-ref --format='%(refname) %(upstream:-none)' refs/heads/
# Test conditional formatting
git for-each-ref --format='%(if)%(HEAD)%(then)* %(end)%(refname:short)' refs/heads/
Terminal window
# Debug pattern matching
git for-each-ref 'refs/heads/*' | wc -l
# Use proper escaping
git for-each-ref 'refs/heads/feature/*'
# Check pattern syntax
git for-each-ref --format='%(refname)' 'refs/heads/main' 2>/dev/null || echo "Pattern failed"
Terminal window
# Fix sorting problems
git for-each-ref --sort=committerdate --format='%(refname)' refs/heads/
# Multiple sort keys
git for-each-ref --sort=authordate --sort=refname refs/heads/
# Reverse sorting
git for-each-ref --sort=-committerdate refs/heads/
Terminal window
# Optimize for large repositories
git for-each-ref --count=100 --sort=-committerdate refs/heads/
# Use specific patterns
git for-each-ref refs/heads/main refs/heads/develop # Faster than refs/heads/
# Avoid expensive operations
# Use %(refname:short) instead of %(subject) for large repos
Terminal window
# Debug filtering
git for-each-ref --contains=HEAD --format='%(refname)' refs/heads/
# Check object existence
if git cat-file -t <object> >/dev/null 2>&1; then
git for-each-ref --contains=<object> refs/heads/
else
echo "Object does not exist"
fi
# Verify merge status
git for-each-ref --merged=HEAD --format='%(refname)' refs/heads/
Terminal window
# Handle encoding problems
git for-each-ref --format='%(refname)' | iconv -f utf-8 -t utf-8
# Use shell quoting for special characters
git for-each-ref --shell --format='%(refname)' refs/heads/
# Handle non-ASCII characters
export LC_ALL=C.UTF-8
git for-each-ref --format='%(refname)' refs/heads/
#!/bin/bash
# Automated branch cleanup
cleanup_branches() {
echo "=== Branch Cleanup Report ==="
# Find merged branches
echo "Branches merged into main:"
git for-each-ref --merged=main --format='%(refname:short)' refs/heads/ | grep -v main | while read -r branch; do
echo " $branch (merged)"
done
# Find old branches
echo "Branches not updated in 6+ months:"
six_months_ago=$(date -d '6 months ago' +%s)
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:unix)' refs/heads/ | while read -r branch date; do
if [ "$date" -lt "$six_months_ago" ] && [ "$branch" != "main" ]; then
echo " $branch (last updated $(date -d "@$date" '+%Y-%m-%d'))"
fi
done
# Interactive cleanup
echo ""
echo "Options:"
echo "1. Delete merged branches"
echo "2. Delete old branches"
echo "3. Show detailed info"
read -p "Choose action (1-3): " action
case "$action" in
1)
git for-each-ref --merged=main --format='%(refname:short)' refs/heads/ | grep -v main | xargs -n1 git branch -d
;;
2)
# Would need more sophisticated logic for safety
echo "Manual review recommended for old branches"
;;
3)
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:relative) %(authorname)' refs/heads/
;;
esac
}
cleanup_branches
Terminal window
# Release branch management
release_management() {
echo "=== Release Branch Management ==="
# Current release branches
echo "Active Release Branches:"
git for-each-ref --sort=-version:refname --format='%(refname:short) %(authordate:relative)' 'refs/heads/release/*' | head -5
# Release tags
echo "Release Tags:"
git for-each-ref --sort=-version:refname --format='%(refname:short) %(authordate:relative) %(subject)' 'refs/tags/v*' | head -10
# Branches ready for release
echo "Branches Ready for Release (merged into main):"
git for-each-ref --merged=main --format='%(refname:short)' refs/heads/ | grep -E '(release|hotfix)' | while read -r branch; do
# Check if already tagged
branch_version=$(echo "$branch" | sed 's/.*release[-/]//;s/.*hotfix[-/]//')
if ! git for-each-ref --format='%(refname)' "refs/tags/v$branch_version" | grep -q .; then
echo " $branch -> ready for v$branch_version"
fi
done
# Release candidates
echo "Release Candidates:"
git for-each-ref --format='%(refname:short)' 'refs/heads/*rc*' | while read -r rc_branch; do
commits=$(git rev-list --count main.."$rc_branch")
echo " $rc_branch ($commits commits since main)"
done
}
release_management
Terminal window
# Generate repository statistics
repo_stats() {
echo "=== Repository Statistics ==="
# Branch statistics
total_branches=$(git for-each-ref refs/heads/ | wc -l)
active_branches=$(git for-each-ref --sort=-committerdate --format='%(committerdate:unix)' refs/heads/ | head -20 | wc -l)
echo "Branches: $total_branches total, $active_branches recently active"
# Tag statistics
total_tags=$(git for-each-ref refs/tags/ | wc -l)
recent_tags=$(git for-each-ref --sort=-authordate --format='%(authordate:unix)' refs/tags/ | awk 'BEGIN{count=0; six_months_ago=systime() - (6*30*24*3600)} $1 > six_months_ago {count++} END{print count}')
echo "Tags: $total_tags total, $recent_tags in last 6 months"
# Remote statistics
total_remotes=$(git for-each-ref refs/remotes/ | wc -l)
remote_names=$(git for-each-ref --format='%(refname)' refs/remotes/ | sed 's|refs/remotes/||' | cut -d/ -f1 | sort | uniq | wc -l)
echo "Remote branches: $total_branches across $remote_names remotes"
# Author statistics
echo "Top Contributors (by commits):"
git for-each-ref --format='%(authorname)' refs/heads/ | sort | uniq -c | sort -nr | head -5 | while read -r count author; do
echo " $author: $count branches"
done
# Branch age distribution
echo "Branch Age Distribution:"
git for-each-ref --format='%(committerdate:unix)' refs/heads/ | awk '
BEGIN {
now = systime()
print "Age ranges:"
}
{
age_days = int((now - $1) / 86400)
if (age_days <= 7) young++
else if (age_days <= 30) month++
else if (age_days <= 90) quarter++
else if (age_days <= 365) year++
else old++
}
END {
print " 0-7 days: " young
print " 8-30 days: " month
print " 31-90 days: " quarter
print " 91-365 days: " year
print " 1+ years: " old
}'
}
repo_stats
Terminal window
# CI/CD pipeline branch validation
ci_branch_validation() {
local required_branches="main develop"
local max_branch_age_days=90
echo "=== CI/CD Branch Validation ==="
# Check required branches exist
for branch in $required_branches; do
if git for-each-ref --format='%(refname)' "refs/heads/$branch" | grep -q .; then
echo "✓ Required branch exists: $branch"
else
echo "✗ Missing required branch: $branch"
return 1
fi
done
# Check branch freshness
stale_branches=$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/heads/ | awk -v max_age="$max_branch_age_days" '
BEGIN {
now = systime()
max_age_seconds = max_age * 24 * 3600
}
{
branch = $1
age_seconds = now - $2
if (age_seconds > max_age_seconds) {
print branch
}
}')
if [ -n "$stale_branches" ]; then
echo "⚠ Stale branches (>${max_branch_age_days} days):"
echo "$stale_branches"
else
echo "✓ All branches are fresh"
fi
# Check for feature branches that should be merged
old_feature_branches=$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' 'refs/heads/feature/*' | awk '
BEGIN {
now = systime()
month_ago = now - (30 * 24 * 3600)
}
$2 < month_ago {
print $1
}')
if [ -n "$old_feature_branches" ]; then
echo "⚠ Old feature branches (>30 days):"
echo "$old_feature_branches"
fi
echo "Branch validation complete"
}
ci_branch_validation
Terminal window
# Custom git commands using for-each-ref
git-branch-age() {
# Show branch ages
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:relative)' refs/heads/
}
git-tag-history() {
# Show tag creation history
git for-each-ref --sort=-authordate --format='%(refname:short) %(authordate) %(authorname)' refs/tags/
}
git-merged-branches() {
# Show branches merged into current
git for-each-ref --merged=HEAD --format='%(refname:short)' refs/heads/ | grep -v "$(git rev-parse --abbrev-ref HEAD)"
}
git-stale-branches() {
# Show stale branches
local days="${1:-30}"
local cutoff_date=$(date -d "$days days ago" +%s)
git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/heads/ | while read -r branch date; do
if [ "$date" -lt "$cutoff_date" ]; then
echo "$branch (last updated $(date -d "@$date" '+%Y-%m-%d'))"
fi
done
}
# Usage examples
git branch-age | head -10
git tag-history | head -5
git merged-branches
git stale-branches 60
Terminal window
# Validate repository mirroring
validate_mirroring() {
local primary_repo="$1"
local mirror_repo="$2"
echo "=== Repository Mirroring Validation ==="
# Compare ref counts
primary_refs=$(GIT_DIR="$primary_repo" git for-each-ref --format='%(refname)' | wc -l)
mirror_refs=$(GIT_DIR="$mirror_repo" git for-each-ref --format='%(refname)' | wc -l)
echo "Primary refs: $primary_refs"
echo "Mirror refs: $mirror_refs"
if [ "$primary_refs" -ne "$mirror_refs" ]; then
echo "⚠ Reference count mismatch"
# Find missing refs
comm -23 <(GIT_DIR="$primary_repo" git for-each-ref --format='%(refname)' | sort) \
<(GIT_DIR="$mirror_repo" git for-each-ref --format='%(refname)' | sort) | while read -r ref; do
echo "Missing in mirror: $ref"
done
else
echo "✓ Reference counts match"
fi
# Compare recent commits
primary_recent=$(GIT_DIR="$primary_repo" git for-each-ref --sort=-committerdate --format='%(refname) %(objectname)' refs/heads/ | head -5)
mirror_recent=$(GIT_DIR="$mirror_repo" git for-each-ref --sort=-committerdate --format='%(refname) %(objectname)' refs/heads/ | head -5)
if [ "$primary_recent" != "$mirror_recent" ]; then
echo "⚠ Recent commits differ"
else
echo "✓ Recent commits match"
fi
echo "Mirroring validation complete"
}
validate_mirroring "/path/to/primary.git" "/path/to/mirror.git"

What’s the difference between for-each-ref and branch -a?

Section titled “What’s the difference between for-each-ref and branch -a?”

for-each-ref provides customizable formatting and advanced filtering; branch -a just lists branch names. for-each-ref can show any ref type with rich metadata.

How do I list all branches with their last commit dates?

Section titled “How do I list all branches with their last commit dates?”

Use git for-each-ref —sort=-committerdate —format=’%(refname:short) %(committerdate:relative)’ refs/heads/

Yes, use refs/remotes/ pattern: git for-each-ref refs/remotes/

Use —format with field specifiers and —shell for shell-quoted output.

What’s the difference between %(refname) and %(refname:short)?

Section titled “What’s the difference between %(refname) and %(refname:short)?”

%(refname) shows full path like refs/heads/main; %(refname:short) shows just main.

Use —sort=version:refname for semantic version sorting.

Can for-each-ref filter by commit content?

Section titled “Can for-each-ref filter by commit content?”

Yes, use —contains= to show refs containing a commit, or —merged= for merged refs.

Use —count= with —sort=-committerdate to show most recent refs.

%(HEAD) shows * for the current HEAD branch, space for others.

Yes, use refs/tags/ pattern for tag references.

Use —exclude= multiple times to exclude matching refs.

What’s the performance impact of for-each-ref?

Section titled “What’s the performance impact of for-each-ref?”

Generally fast, but complex formats on large repos can be slow. Use —count and specific patterns to optimize.

Can for-each-ref show upstream information?

Section titled “Can for-each-ref show upstream information?”

Yes, use %(upstream) and %(upstream:short) for tracking branch info.

How do I find branches that contain a specific commit?

Section titled “How do I find branches that contain a specific commit?”

Use git for-each-ref —contains= refs/heads/

No, for-each-ref shows current ref state. Use git reflog for historical information.

Use —stdin option to read patterns from standard input.

—include-root-refs includes refs that are direct children of refs/ (rarely needed).

Yes, use %(objectsize) in format string.

Use —sort=committerdate (ascending) to show oldest first.

Yes, it works with both loose and packed refs transparently.

Applications of the git for-each-ref command

Section titled “Applications of the git for-each-ref command”
  1. Repository Analysis: Comprehensive ref inspection and statistics
  2. Branch Management: Automated branch cleanup and status reporting
  3. CI/CD Integration: Branch validation and automated workflows
  4. Custom Reporting: Flexible ref information display and formatting
  5. Repository Maintenance: Health checks and cleanup operations
  6. Script Integration: Programmatic access to ref information