You essentially have a choice to make: do you wish to make everyone use the replacement references, or do you prefer to rewrite the entire repository and make everyone have a big flag-day during which they switch from "old repository" to "new repository"? Neither method is particularly fun or profitable. :-)
How replacements work
What git replace
does is to add a new object into the Git repository and give it a name in the refs/replace/
name-space. The name in this name-space is the hash ID of the object that the new object replaces. For instance, if you're replacing commit 1234567...
, the name of the new object (whose ID is not 1234567...
—for concreteness, let's say it's fedcba9...
instead) is refs/replace/1234567...
.
The rest of Git, when looking for objects, checks first to see if there is a refs/replace/<hash-id>
object. If so (and replacing is not disabled), the rest of Git then returns the object to which the refs/replace/
name points, instead of the actual object. So when some other part of Git reads some commit that says "my parent commit is 1234567...
", that other part of Git goes to find 1234567...
, sees that refs/replace/1234567...
exists, and returns object fedcba9...
instead. You then see the replacement.
If you do not have the reference refs/replace/1234567...
, though, your Git never swaps in the replacement object. (This is true whether or not you have the replacement object. It's the reference itself that causes the replacement to occur. Having the reference guarantees that you have the object.)
Hence, for some other Git to execute this same replacement process, you must deliver the refs/replace/
reference to that other Git.
Transferring replacements from one Git to another
In general, you would push such objects with:
git push <repository> 'refs/replace/*:refs/replace/*'
(or specifically list the one replace reference you wish to push). To fetch these objects:
git fetch <repository> 'refs/replace/*:refs/replace/*'
(You can add this fetch refspec to the fetch
configuration in each clone. Using git fetch
or git fetch <repository>
will then automatically pick up any new replacement objects pushed. Pushing is still a pain, and of course this step has to be repeated on each new clone.)
Note that neither refspec here sets the force flag. It's up to you whether you want to force-overwrite existing refs/replace/
references, should such a thing happen.
Rewriting a repository
Alternatively, once you have replacements in place, you can run a repository-copying operation—by this, I mean a commit-by-commit copy, not a fast copy like git clone --mirror
—such as git filter-branch
. If this copying operation is run without disabling replacements, the replaced objects are not copied; instead, their replacements are copied. Hence:
git filter-branch --tag-name-filter cat -- --all
has the side effect of "cementing replacements" forever in the copied repository. You may then discard all the original references and all the replacement references. (The easy way to do this is to clone the filtered repository.)
Of course, since this is a new and different repository, it is not compatible with the original repository or any of its clones. But it no longer requires careful coordination of the refs/replace/
name-space (since it no longer has any replacement objects!).