This Git warning appears when your repository uses the deprecated grafts file to modify commit history. Git 2.46+ actively warns about this feature, which will be removed in Git 3.0. Convert grafts to git replace refs for a permanent fix.
Git grafts were an early mechanism for rewriting commit parentage without modifying the actual commits. By placing entries in the `.git/info/grafts` file, developers could make Git treat certain commits as having different parents than what was recorded in the commit objects themselves. This was commonly used for joining separate repository histories or hiding early commits. However, grafts have fundamental limitations that make them problematic for modern Git workflows. The most significant issue is that grafts are purely local - they exist only in the `.git/info/grafts` file and cannot be shared through push or pull operations. This means every developer on a team would need to manually configure identical grafts, and cloned repositories would have different history than the original. Git marked grafts as deprecated starting with Git 2.18 (June 2018), recommending `git replace` as the modern alternative. Starting with Git 2.46, Git actively displays this warning whenever it detects an active grafts file. The feature is scheduled for complete removal in Git 3.0, making migration essential for any repository still using grafts.
First, examine your grafts file to understand what history modifications are in place:
# View the contents of the grafts file
cat .git/info/grafts
# Show the graft points in your history
git log --oneline --decorate | grep -i graft
# Check how many graft entries exist
wc -l .git/info/graftsEach line in the grafts file has the format: <commit> <parent1> [<parent2>...]. A commit with no parents listed after it becomes a root commit (orphan).
Git provides a built-in command to convert all graft entries to replace refs:
# Convert all grafts to replace refs
git replace --convert-graft-fileThis command reads each entry in .git/info/grafts and creates a corresponding replace ref. After conversion, Git no longer reads from the grafts file.
Verify the conversion worked:
# List all replace refs
git replace -l
# Show details of a specific replace ref
git replace -l | xargs -I {} git log -1 {}Unlike grafts, replace refs can be shared through Git's normal push mechanism:
# Push all replace refs to the remote
git push origin 'refs/replace/*:refs/replace/*'For team members to receive the replace refs, they need to fetch them explicitly:
# Configure fetch to include replace refs
git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
# Fetch the replace refs
git fetch originAdd this fetch configuration to your team's setup documentation or onboarding scripts.
Once you've verified the replace refs are working correctly, remove the grafts file:
# Backup the grafts file first
cp .git/info/grafts .git/info/grafts.backup
# Remove the grafts file
rm .git/info/grafts
# Verify the warning no longer appears
git status
git log --oneline -5If you encounter issues, restore from the backup:
mv .git/info/grafts.backup .git/info/graftsFor a permanent solution that removes the need for replace refs entirely, use git filter-repo to rewrite history:
# Install git-filter-repo (if not already installed)
pip install git-filter-repo
# Create a fresh clone to work with
git clone --no-local original-repo filtered-repo
cd filtered-repo
# Configure replace refs if not already fetched
git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
git fetch origin
# Rewrite history to make replace refs permanent
git filter-repo --forceWarning: This rewrites commit hashes. All team members will need to re-clone the repository after force-pushing.
If your CI/CD pipelines depend on the modified history, update them to fetch replace refs:
GitHub Actions:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch replace refs
run: |
git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
git fetch originGitLab CI:
before_script:
- git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
- git fetch originConsider running git filter-repo to make the history permanent and eliminate the need for these extra fetch steps.
Confirm that grafts have been fully migrated and the warning no longer appears:
# Check that grafts file is gone
ls -la .git/info/grafts 2>/dev/null || echo "Grafts file removed"
# Verify replace refs exist
git replace -l
# Run various commands to ensure no warnings
git status
git log --oneline -10
git fsck
# Clone fresh and verify history matches
git clone --no-local . /tmp/test-clone
cd /tmp/test-clone
git log --oneline -10The warning should no longer appear, and repository history should be consistent across clones.
### Understanding git replace vs grafts
While grafts simply told Git to pretend a commit had different parents, git replace is more powerful - it can replace any Git object (commit, tree, blob, or tag) with another. When you run git replace --convert-graft-file, Git:
1. Creates a new commit object with the modified parent information
2. Stores a reference from the original commit SHA to the replacement commit
3. Transparently substitutes the replacement whenever the original is accessed
This means the original commits remain intact in the object database, providing a safety net.
### Sharing replace refs in large teams
For organizations with many developers, consider these approaches:
# Option 1: Use a Git template for new clones
mkdir -p ~/.git-template/hooks
git config --global init.templateDir ~/.git-template
# Add a post-checkout hook that fetches replace refs
# Option 2: Include in .gitconfig checked into repo
# In .gitconfig at repo root:
[remote "origin"]
fetch = +refs/replace/*:refs/replace/*### When filter-repo is the better choice
Consider using git filter-repo instead of git replace when:
- The repository is relatively small and a full rewrite is feasible
- You want to eliminate the complexity of sharing replace refs
- You're already planning other history cleanup (removing large files, secrets, etc.)
- You need the changes to persist through GitHub/GitLab forks
### Migrating from SVN with grafts
If your grafts were created during SVN migration, the graft points typically mark where svn copy operations (branches) were converted. These are important for maintaining correct git log --follow behavior. Test thoroughly after conversion:
# Test that file history traverses correctly across graft points
git log --follow --oneline -- path/to/frequently-branched-file### Handling merge conflicts with replace refs
If you have ongoing work based on the old history, you may encounter issues after pushing replace refs. Team members should:
1. Finish and push any in-progress work before the migration
2. Delete local branches and re-fetch them after replace refs are shared
3. Avoid rebasing across the graft/replace boundary
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server
fatal: unable to access: Proxy auto-configuration failed
How to fix 'Proxy auto-configuration failed' in Git
fatal: unable to access: Authentication failed (proxy requires basic auth)
How to fix 'Authentication failed (proxy requires basic auth)' in Git
fatal: unable to access: no_proxy configuration not working
How to fix 'no_proxy configuration not working' in Git
fatal: unable to read tree object in treeless clone
How to fix 'unable to read tree object in treeless clone' in Git