Skip to content

read-tree Git Command Guide

The git read-tree command reads tree information from the object database into the index, optionally performing merges. It is a low-level plumbing command used by higher-level Git operations like checkout and merge.

Terminal window
git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>)
[-u | -i]] [--index-output=<file>] [--no-sparse-checkout]
(--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
OptionDescription
-mPerform merge operation
--resetReset merge (discard unmerged entries)
--trivialAllow trivial merges only
--aggressiveResolve more merge conflicts
OptionDescription
-uUpdate working tree after merge
-iDon’t exit on conflicts (internal use)
OptionDescription
--emptyEmpty the index
--prefix=<prefix>Read tree into subdirectory
--index-output=<file>Write index to file
--no-sparse-checkoutDisable sparse checkout
ParameterDescription
<tree-ish1>Primary tree to read
<tree-ish2>Second tree for merge
<tree-ish3>Third tree for 3-way merge
Git's Tree Structure:
├── Working Tree: Actual files on disk
├── Index: Staging area (next commit)
├── HEAD: Current branch commit
├── Trees: Immutable file system snapshots
Read-Tree Operations:
├── Single tree: Replace index contents
├── Two trees: Fast-forward merge
├── Three trees: Full 3-way merge
└── With -u: Update working tree too
Merge Types Supported:
├── Trivial: No conflicts, automatic resolution
├── Fast-forward: Linear history merge
├── 3-way: Complex merge with common ancestor
└── Conflicted: Manual resolution required
Index States:
├── Stage 0: Common ancestor (merge base)
├── Stage 1: Current branch version
├── Stage 2: Other branch version
├── Stage 3: Working tree (if conflicts)
Read-Tree Workflow:
1. Parse tree-ish arguments to tree objects
2. Load tree contents into index
3. Handle merge logic if -m specified
4. Update working tree if -u specified
5. Report conflicts if any
6. Exit with appropriate status
Terminal window
# Read tree into index (replace current index)
git read-tree <tree-ish>
# Read tree from specific commit
git read-tree HEAD~1
# Read tree from branch
git read-tree main
# Read tree from tag
git read-tree v1.0.0
# Empty the index
git read-tree --empty
Terminal window
# Fast-forward merge (2-way)
git read-tree -m HEAD <other-tree>
# 3-way merge with common ancestor
git read-tree -m <ancestor> HEAD <other-tree>
# Merge with working tree update
git read-tree -m -u HEAD <other-tree>
# Reset merge (discard conflicts)
git read-tree --reset -u HEAD <other-tree>
Terminal window
# Read tree into subdirectory
git read-tree --prefix=vendor/ <vendor-tree>
# Read multiple trees into different prefixes
git read-tree --prefix=src/ <src-tree>
git read-tree --prefix=tests/ <test-tree>
# Combine with merge
git read-tree -m --prefix=submodule/ HEAD <submodule-tree>
Terminal window
# Allow trivial merges only
git read-tree -m --trivial HEAD <other-tree>
# Aggressive conflict resolution
git read-tree -m --aggressive HEAD <other-tree>
# Handle merge conflicts
git read-tree -m HEAD <conflicting-tree>
# Check status for conflicts
git status
# Resolve conflicts manually
# Edit conflicted files
git add resolved-file.txt
# Continue with resolved index
Terminal window
# Merge subtree using read-tree
subtree_merge() {
local subtree_path="$1"
local subtree_repo="$2"
echo "Merging subtree: $subtree_path from $subtree_repo"
# Read subtree into prefix
git read-tree --prefix="$subtree_path/" "$subtree_repo"
# Add merge commit
git commit -m "Merge subtree $subtree_repo into $subtree_path"
echo "Subtree merge complete"
}
subtree_merge "vendor/library" "library-repo/main"
Terminal window
# Create custom index state
custom_index() {
local output_file="$1"
# Read tree to custom index file
git read-tree --index-output="$output_file" <tree-ish>
# Use custom index
GIT_INDEX_FILE="$output_file" git status
echo "Custom index created: $output_file"
}
custom_index "/tmp/custom-index"
Terminal window
# Simulate sparse checkout with read-tree
sparse_simulation() {
local allowed_paths="$1"
echo "Simulating sparse checkout for: $allowed_paths"
# Start with empty index
git read-tree --empty
# Add allowed paths
for path in $allowed_paths; do
git read-tree --prefix="$path/" HEAD:"$path/"
done
# Checkout allowed files
git checkout-index -a
echo "Sparse checkout simulation complete"
}
sparse_simulation "src/ tests/"
Terminal window
# Configure merge behavior
git config merge.trivial true # Allow trivial merges
git config merge.aggressive false # Disable aggressive merges
# Configure index handling
git config core.sparseCheckout true # Enable sparse checkout
git config index.sparse true # Sparse index
# Configure working tree updates
git config checkout.updateWorkingTree true # Update working tree
Terminal window
# Always check state before operations
git status
git diff
# Backup index before complex operations
cp .git/index .git/index.backup
# Verify tree exists
if git cat-file -t "$tree" | grep -q "tree"; then
git read-tree "$tree"
else
echo "Invalid tree: $tree"
exit 1
fi
# Check for conflicts after merge
if git read-tree -m HEAD "$other_tree" 2>/dev/null; then
echo "Merge successful"
else
echo "Merge conflicts detected"
git status
fi
Terminal window
# Use aggressive mode for complex merges
git read-tree -m --aggressive HEAD <complex-tree>
# Avoid working tree updates when possible
git read-tree -m HEAD <tree> # No -u flag
# Use trivial mode for simple merges
git read-tree -m --trivial HEAD <simple-tree>
# Batch operations
for tree in tree1 tree2 tree3; do
git read-tree "$tree"
# Process...
done
#!/bin/bash
# Custom merge strategy using read-tree
custom_merge_strategy() {
local ours="$1"
local theirs="$2"
local base="${3:-}"
echo "Custom merge: $ours + $theirs"
if [ -n "$base" ]; then
# 3-way merge
if git read-tree -m "$base" "$ours" "$theirs"; then
echo "3-way merge successful"
else
echo "3-way merge failed - conflicts detected"
return 1
fi
else
# 2-way merge
if git read-tree -m "$ours" "$theirs"; then
echo "2-way merge successful"
else
echo "2-way merge failed"
return 1
fi
fi
# Update working tree
git checkout-index -a
echo "Custom merge complete"
}
custom_merge_strategy "main" "feature" "common-ancestor"
Terminal window
# Perform repository surgery with read-tree
repo_surgery() {
local surgery_type="$1"
case "$surgery_type" in
"split-subdir")
# Split subdirectory into separate history
local subdir="$2"
local new_repo="$3"
echo "Splitting $subdir into $new_repo"
# Create new repository
git init "$new_repo"
cd "$new_repo"
# Read only subdirectory history
git read-tree --prefix="$subdir/" HEAD
git checkout-index -a
# Create initial commit
git add .
git commit -m "Split from parent repository"
cd -
echo "Subdirectory split complete"
;;
"graft-history")
# Graft history from another repository
local source_repo="$2"
local graft_point="$3"
echo "Grafting history from $source_repo at $graft_point"
# Read source history
git remote add source "$source_repo"
git fetch source
# Graft histories
git read-tree --prefix=grafted/ source/main
git checkout-index -a
echo "History grafting complete"
;;
esac
}
repo_surgery "split-subdir" "utils" "/tmp/utils-repo"
Terminal window
# Manage complex index states
index_management() {
local operation="$1"
case "$operation" in
"backup")
# Backup current index
cp .git/index .git/index.backup
echo "Index backed up"
;;
"restore")
# Restore index from backup
cp .git/index.backup .git/index
echo "Index restored"
;;
"merge-indexes")
# Merge two index states
local index1="$2"
local index2="$3"
# Read first index
git read-tree --index-output=/tmp/merged-index "$index1"
# Merge with second
GIT_INDEX_FILE=/tmp/merged-index git read-tree -m "$index1" "$index2"
# Replace current index
mv /tmp/merged-index .git/index
echo "Indexes merged"
;;
"clean-index")
# Clean index of specific patterns
git read-tree --empty
git add . # Re-add wanted files
echo "Index cleaned"
;;
esac
}
index_management "backup"
Terminal window
# Handle read-tree merge conflicts
resolve_read_tree_conflicts() {
echo "Resolving read-tree merge conflicts"
# Check conflicted files
git status
# Files will be in unmerged state
git ls-files -u
# Resolve each conflict
for file in $(git ls-files -u | awk '{print $4}' | sort | uniq); do
echo "Resolving conflicts in: $file"
# Show conflict details
git show :1:"$file" # Common ancestor
git show :2:"$file" # Our version
git show :3:"$file" # Their version
# Edit file manually or use merge tool
# Then stage resolution
git add "$file"
done
echo "All conflicts resolved"
}
resolve_read_tree_conflicts
Terminal window
# Handle invalid tree references
validate_tree_reference() {
local tree_ref="$1"
echo "Validating tree reference: $tree_ref"
# Check if reference exists
if ! git rev-parse --verify "$tree_ref" >/dev/null 2>&1; then
echo "Reference does not exist: $tree_ref"
return 1
fi
# Check if it's a tree object
local object_type
object_type=$(git cat-file -t "$tree_ref")
case "$object_type" in
"tree")
echo "Valid tree object"
return 0
;;
"commit")
# Extract tree from commit
tree_ref="$tree_ref^{tree}"
echo "Converted commit to tree: $tree_ref"
return 0
;;
*)
echo "Invalid object type: $object_type (expected tree or commit)"
return 1
;;
esac
}
validate_tree_reference "HEAD"
Terminal window
# Fix corrupted or inconsistent index
repair_index_state() {
echo "Repairing index state"
# Backup current index
cp .git/index .git/index.corrupted
# Reset index to HEAD
git read-tree HEAD
# Check if working tree matches
if git diff --quiet; then
echo "Index repaired successfully"
else
echo "Working tree differs from HEAD"
# Decide whether to update working tree
read -p "Update working tree to match HEAD? (y/N): " update
if [[ "$update" == "y" ]]; then
git checkout-index -a
echo "Working tree updated"
fi
fi
# Verify index integrity
git fsck --full --strict
}
repair_index_state
Terminal window
# Optimize read-tree performance
optimize_read_tree_performance() {
echo "Optimizing read-tree performance"
# Use trivial merges when possible
git read-tree -m --trivial HEAD <other-tree>
# Avoid working tree updates
git read-tree -m HEAD <other-tree> # No -u
# Use aggressive mode for complex merges
git read-tree -m --aggressive HEAD <complex-tree>
# Process large trees in batches
find .git/objects -name "*.tree" | head -100 | while read -r tree; do
git read-tree "$tree" 2>/dev/null || true
done
echo "Performance optimization complete"
}
optimize_read_tree_performance
Terminal window
# Handle large tree operations
handle_large_trees() {
echo "Handling large tree operations"
# Increase Git's memory limits
export GIT_ALLOC_LIMIT=2g
# Use system resources efficiently
ulimit -v 4000000 # 4GB virtual memory limit
# Process in smaller chunks
git read-tree --prefix=chunk1/ HEAD:large-dir/part1
git read-tree --prefix=chunk2/ HEAD:large-dir/part2
# Clean up after large operations
git gc --quiet
echo "Large tree operation complete"
}
handle_large_trees
#!/bin/bash
# Implement custom merge strategy with read-tree
advanced_merge() {
local branch1="$1"
local branch2="$2"
local strategy="${3:-recursive}"
echo "Advanced merge: $branch1 + $branch2 using $strategy"
# Find merge base
local base
base=$(git merge-base "$branch1" "$branch2")
if [ -z "$base" ]; then
echo "No common ancestor found"
return 1
fi
echo "Merge base: $base"
case "$strategy" in
"ours")
# Keep our changes
git read-tree -m -u "$branch1"
echo "Merge completed with 'ours' strategy"
;;
"theirs")
# Keep their changes
git read-tree -m -u "$branch2"
echo "Merge completed with 'theirs' strategy"
;;
"recursive")
# Standard recursive merge
if git read-tree -m "$base" "$branch1" "$branch2"; then
# Check for conflicts
if git ls-files -u | grep -q .; then
echo "Merge conflicts detected - manual resolution needed"
return 1
else
git checkout-index -a
echo "Recursive merge completed successfully"
fi
else
echo "Recursive merge failed"
return 1
fi
;;
"octopus")
# Multi-branch merge
local all_branches="$branch1 $branch2 $base"
if git read-tree -m $all_branches; then
git checkout-index -a
echo "Octopus merge completed"
else
echo "Octopus merge failed"
return 1
fi
;;
esac
# Create merge commit
git commit --no-edit -m "Merge $branch1 and $branch2"
}
advanced_merge "feature/auth" "feature/ui" "recursive"
Terminal window
# Restructure repository layout with read-tree
repo_restructure() {
local operation="$1"
case "$operation" in
"flatten")
# Flatten directory structure
echo "Flattening repository structure"
# Read all subdirectories
find . -type d -name "*" | while read -r dir; do
if [ "$dir" != "." ]; then
git read-tree --prefix="flattened/$(basename "$dir")/" HEAD:"$dir"
fi
done
# Update working tree
git checkout-index -a
echo "Repository flattened"
;;
"split-by-type")
# Split files by type
echo "Splitting files by type"
# Group by extension
for ext in c h js py; do
mkdir -p "by-type/$ext"
find . -name "*.$ext" -type f | while read -r file; do
git read-tree --prefix="by-type/$ext/$(basename "$file")" HEAD:"$file"
done
done
echo "Files split by type"
;;
"create-submodules")
# Convert directories to submodules
local subdirs="lib utils tools"
for subdir in $subdirs; do
echo "Converting $subdir to submodule"
# Create separate repository
mkdir -p "../${subdir}-repo"
cd "../${subdir}-repo"
git init
git read-tree --prefix="$subdir/" ../main-repo/HEAD:"$subdir"
git checkout-index -a
git add .
git commit -m "Initial commit for $subdir"
cd "../main-repo"
# Replace directory with submodule
rm -rf "$subdir"
git submodule add "../${subdir}-repo" "$subdir"
done
echo "Submodules created"
;;
esac
}
repo_restructure "flatten"
Terminal window
# Version different index states
index_versioning() {
local version_name="$1"
echo "Creating index version: $version_name"
# Save current index state
cp .git/index ".git/index.$version_name"
# Create version file
cat > ".git/index-versions.txt" << EOF
Version: $version_name
Date: $(date)
Commit: $(git rev-parse HEAD)
Files: $(git ls-files | wc -l)
EOF
echo "Index version saved: $version_name"
# List available versions
echo "Available index versions:"
ls -la .git/index.* 2>/dev/null | while read -r line; do
version=$(basename "$line" | sed 's/index\.//')
echo " $version"
done
}
index_versioning "pre-refactor"
Terminal window
# Implement custom checkout with read-tree
custom_checkout() {
local target="$1"
local sparse_paths="${2:-}"
echo "Custom checkout to: $target"
# Validate target
if ! git rev-parse --verify "$target" >/dev/null 2>&1; then
echo "Invalid target: $target"
return 1
fi
# Read tree into index
if [ -n "$sparse_paths" ]; then
# Sparse checkout
git read-tree --empty
for path in $sparse_paths; do
git read-tree --prefix="$path/" "$target":"$path"
done
else
# Full checkout
git read-tree "$target"
fi
# Update working tree
git checkout-index -a
# Update HEAD if switching branches
if git show-ref --verify --quiet "refs/heads/$target"; then
git update-ref HEAD "refs/heads/$target"
fi
echo "Custom checkout complete"
}
custom_checkout "main" "src/ tests/"
Terminal window
# Perform conflict-free merges
conflict_free_merge() {
local source_branch="$1"
local target_branch="${2:-HEAD}"
echo "Attempting conflict-free merge: $source_branch -> $target_branch"
# Check for potential conflicts without merging
if git merge-tree --no-commit "$target_branch" "$source_branch" >/dev/null 2>&1; then
echo "No conflicts detected - proceeding with merge"
# Perform the merge
git read-tree -m -u "$target_branch" "$source_branch"
# Create merge commit
git commit -m "Merge $source_branch into $target_branch"
echo "Conflict-free merge completed"
else
echo "Conflicts detected - cannot perform automatic merge"
# Show conflict details
git merge-tree "$target_branch" "$source_branch"
return 1
fi
}
conflict_free_merge "feature/fix-bug"

What’s the difference between read-tree and checkout?

Section titled “What’s the difference between read-tree and checkout?”

read-tree updates the index from tree objects; checkout switches branches and updates both index and working tree. read-tree is lower-level and doesn’t change HEAD.

How do I merge two branches with read-tree?

Section titled “How do I merge two branches with read-tree?”

Use git read-tree -m for a 2-way merge, or git read-tree -m for a 3-way merge.

-u updates the working tree after the index operation, equivalent to running checkout-index -a afterwards.

Yes, but it only performs trivial merges. Conflicting paths are left in an unmerged state in the index for manual resolution.

Use git read-tree —empty to remove all entries from the index.

—prefix reads a tree into a subdirectory of the index, useful for combining multiple repositories or creating subtrees.

Yes, as long as the remote tracking branches exist locally. Use git fetch first to ensure remote branches are available.

Check the exit code - 0 means success, non-zero indicates conflicts or errors. Use git status to see the result.

What’s the difference between -m and —reset?

Section titled “What’s the difference between -m and —reset?”

-m performs a merge and fails if there are unmerged entries; —reset performs a merge but discards unmerged entries instead of failing.

Yes, with the -u option. Without -u, it only updates the index.

How do I resolve conflicts after read-tree?

Section titled “How do I resolve conflicts after read-tree?”

Edit the conflicted files, then use git add to stage the resolved versions. The index tracks conflict resolution.

—trivial restricts merges to only trivial cases (no conflicts), failing if any conflicts would occur.

Yes, tags that point to commits can be used, and Git will use the tree object from the commit.

Use git status to see index state, git diff to see working tree changes, and git ls-files -u to see unmerged files.

—index-output writes the resulting index to a file instead of .git/index, useful for testing or creating custom index states.

Yes, it’s designed for scripting. It has predictable behavior and clear exit codes for different scenarios.

  1. Low-level Index Management: Direct manipulation of the index for custom operations
  2. Custom Merge Strategies: Implement specialized merge behaviors not available in standard Git
  3. Repository Restructuring: Split, combine, or reorganize repository structures
  4. Subtree Operations: Manage complex subtree merging and manipulation
  5. Sparse Checkout Implementation: Create custom working directory populations
  6. Advanced Conflict Resolution: Handle merge conflicts at the index level