Editing history without rewriting it
Sometimes you want to splice histories together (a converted Subversion import meeting a continuation, for example) without rewriting commits. Grafts and the refs/replace/ namespace let Git pretend the parent of one commit is a different commit, leaving the original objects untouched.
Grafts (legacy)
.git/info/grafts is a plaintext file: each line specifies a commit's parents. Git applies these overrides when traversing history. Grafts are deprecated in favor of replace refs because they are local-only and confusing.
# .git/info/grafts
<child-sha> <new-parent-sha>
Replace refs
Replace refs are real refs under refs/replace/. They map an "original" object SHA to a "replacement" object SHA. Unlike grafts, they can be pushed and shared. Most commands honor them automatically.
git replace --graft <child> <new-parent>
git replace --graft v3.0.0 v2.99.0
git replace --convert-graft-file
git replace -d <sha>
git replace -l
--graft creates a replacement commit identical to the original except for the parents.
Use case: stitch imports
You imported old SVN history as a separate orphan branch, and the modern repo's first commit has no parents. Splice them:
git replace --graft $(git rev-list --max-parents=0 main | head -1) svn-history-tip
Now git log main walks all the way back through the SVN era.
Sharing
Push replace refs explicitly:
git push origin 'refs/replace/*:refs/replace/*'
Collaborators fetching the same refspec see the same grafted view.
Disabling temporarily
git --no-replace-objects log
GIT_NO_REPLACE_OBJECTS=1 git log
Useful when you need the genuine, unspliced history for forensic checks.
Common mistakes
Confusing replace with rewrite — replace does not change object SHAs, so signatures remain valid but the "real" parent of a commit is not what you see. Forgetting to push replace refs means your join only exists locally. Grafts in .git/info/grafts are no longer recommended; convert them with git replace --convert-graft-file.
Common with filter-repo
If you want a permanent rewrite rather than a runtime view, use filter-repo to materialize the new history. See "filter-repo: rewriting history safely".
Related
See "Git internals: the object database" and "Git internals: references and the reflog".