By admin , 29 April 2026

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".