Test Alternate Git Objects Safely with git replace
You can use git replace to make Git read one object in place of another without changing the branch, tag, or original object. This is useful for testing a repaired commit, examining an alternate tree, or trying a different parentage before making a permanent history change. The result is local to the repository and is controlled by refs under refs/replace/.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide targets Git 2.43.0, the version installed with the local git-man package. Allow about 10 minutes if you already have the two object IDs. No command here needs elevated privileges.
Before you start
- Work in the repository whose object database contains both objects.
- Choose an object to hide temporarily and a replacement object of the same Git type.
- Save the two full object IDs. Short IDs may work for normal Git revision parsing, but full IDs make a replacement reference explicit and easier to audit.
For the examples below, replace the values in these shell variables. They are placeholders, not commands that discover suitable objects for you.
OLD_OBJECT=1111111111111111111111111111111111111111
NEW_OBJECT=2222222222222222222222222222222222222222
Checkpoint: inspect the objects first
Check that both IDs exist and identify their types before creating anything. This avoids a confusing failure later and catches the most common mistake, attempting to replace a commit with a tree or blob.
git cat-file -t "$OLD_OBJECT"
git cat-file -t "$NEW_OBJECT"
git cat-file -p "$OLD_OBJECT" | sed -n '1,12p'
git cat-file -p "$NEW_OBJECT" | sed -n '1,12p'
For a commit, the first command prints commit; for a tree or file object it prints tree or blob. The two type outputs must match unless you deliberately use --force. For initial testing, do not bypass that safeguard.
Create the replacement
Run the two-object form to create a replacement reference. Git names the reference after OLD_OBJECT and stores NEW_OBJECT as its target. The original object remains in the object database.
git replace "$OLD_OBJECT" "$NEW_OBJECT"
A successful creation is normally silent. If a replacement reference already exists for the old object, the command fails rather than overwriting it. That is a useful stop: inspect the existing mapping before deciding whether it is yours to change.
git replace --format=long -l "$OLD_OBJECT"
Expected output has this shape, with your actual IDs and object type:
1111111111111111111111111111111111111111 (commit) -> 2222222222222222222222222222222222222222 (commit)
Checkpoint: prove which object Git is reading
Most Git commands now follow the replacement by default. A commit-oriented check makes the change visible without moving a branch.
git show --no-patch --format='%H %s' "$OLD_OBJECT"
git --no-replace-objects show --no-patch --format='%H %s' "$OLD_OBJECT"
The first command reports the replacement commit's subject. The second disables replacement lookup for that invocation and reports the original commit's subject. The option must appear immediately after git. The environment variable GIT_NO_REPLACE_OBJECTS=1 provides the same bypass for commands launched with that environment.
Replacement references are not a general transport mechanism. The manpage excludes reachability operations such as pruning, pack transfer, and fsck from replacement lookup. Do not treat a successful local inspection as proof that another clone, a fetch, or repository maintenance will see the same view.
List and filter mappings
With no arguments, git replace lists all replacement references in short form, showing the replaced IDs. Use a format when the target or object types matter.
git replace
git replace --format=medium -l
git replace --format=long -l "$OLD_OBJECT"
shortshows the replaced object ID.mediumshows the old and replacement IDs.longadds each object's type.
A pattern passed to -l limits the listed object names. If a filter prints nothing, check the ID and the repository before assuming the mapping has disappeared.
Remove the mapping
Deleting the replacement reference restores normal lookup. This does not delete either object, branch, or commit.
git replace --delete "$OLD_OBJECT"
git replace --format=long -l "$OLD_OBJECT"
The delete command prints a message such as Deleted replace ref '...'. The following list should then produce no output. If you need to preserve the mapping for later, record the full output of git replace --format=medium -l first. There is no separate undo command, but recreating the mapping with the recorded IDs is the practical recovery.
Other ways to create replacements
git replace --edit OBJECT pretty-prints the existing object to a temporary file, opens the configured editor, parses the result, and creates a replacement object of the same type. Use this only when you understand the object's format. The --raw option supplies raw contents while editing; the installed documentation says this currently affects trees and may require an editor that handles binary data cleanly.
git replace --graft COMMIT PARENT... creates a new commit with the supplied parents and replaces the named commit with it. With no parent arguments, it can create a parentless replacement. Treat this as history surgery for local analysis, not as a shared rewrite. The command git replace --convert-graft-file converts entries in .git/info/grafts to replacement commits and deletes that graft file after success, so back up that file before using it.
Failure traps and safety boundaries
- Different types: a commit, tree, and blob cannot normally replace one another. Fix the object selection instead of reaching for
--force. - Unexpected history: replacement affects commands that resolve or traverse ordinary objects. A command can therefore show a history different from the branch's stored history.
- Hard reset: the manpage warns that
git reset --hardto a replaced commit can move the branch to the replacement commit. Avoid it while a mapping is active, or remove the mapping and verify the target before resetting. - Overwriting:
git replace --force OLD NEWoverwrites an existing mapping. This is destructive to the previous mapping, so capture it withgit replace --format=long -l OLDfirst.
Done means
- The old and new objects exist and have matching types.
git replace --format=long -lshows the intended mapping.- A normal Git command shows the replacement, while
git --no-replace-objectsshows the original. - The mapping is deleted when the experiment ends, and a final listing confirms no stale replacement remains.