Subject: Re: clone breaks replace

From: Jonathan Nieder <[email-masked]>
Date: Fri, 7 Jan 2011 14:51:03 -0600

Phillip Susi wrote:

> Isn't the whole purpose of using replace to avoid having to use
> filter-branch, which throws out all of the existing commit records, and
> creates an entirely new commit chain that is slightly modified?

No.  What documentation suggested that?  Maybe it can be fixed.

The original purpose of grafts (the ideological ancestor of
replacement refs) was to serve a very particular use case.  Sit down
by the fire, if you will, and...

Git had just came into existence and pack files did not exist yet.  A
full import of the Linux kernel history was possible but the result
was enormous and not something ready to be imposed on all Linux
contributors.  So what can one do?

 $ git show -s v2.6.12-rc2^0
 commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2
 Author: Linus Torvalds <[email-masked]>
 Date:   Sat Apr 16 15:20:36 2005 -0700

     Linux-2.6.12-rc2

     Initial git repository build. I'm not bothering with the full history,
     even though we have it. We can create a separate "historical" git
     archive of that later if we want to, and in the meantime it's about
     3.2GB when imported into git - space that would just make the early
     git days unnecessarily complicated, when we don't have a lot of good
     infrastructure for it.

     Let it rip!

Fast forward three months, and there is discussion[1] about what to do
with the historical git archive.  A clever idea: teach git to _pretend_
that the historical archive is the parent to v2.6.12-rc2, so
"git log --grep", "gitk", and so on work as they ought to.

So grafts were born.  One of the nicest advantages of grafts is that
they make it easy to do complex history surgery: make some grafts ---
cut here, paste there --- and then run "git filter-branch" to make it
permanent.

But grafts have a serious problem.

Transport machinery needs to ignore grafts --- otherwise, the two ends
of a connection could have different ideas of the history preceding a
commit, resulting in confusion and breakage.  A fix to that was
finally grafted on a few years later (see also [2]).

 $ GIT_NOTES_REF=refs/remotes/charon/notes/full \
   git log --grep=graft --grep=repack --all-match --no-merges
 [...]
     git repack: keep commits hidden by a graft
 [...]
     Archived-At: <http://thread.gmane.org/gmane.comp.version-control.git/123874>

There is also the problem that grafts are too "raw": it is very easy
to make a graft pointing to a nonexistent object, say.  And meanwhile
git has no native support for transfering grafts over the wire.

In that context there emerged the nicer (imho) refs/replace mechanism:

 - reachability checking and transport machinery can treat them like
   all other references --- no need for low-level tools to pay
   attention to the artificial history;
 - easy to script around with "git replace" and "git for-each-ref"
 - can choose to fetch or not fetch with the usual
   "git fetch repo refs/replace/*:refs/replace/*" syntax

Common applications:

 - locally staging history changes that will later be made permanent
   with "git filter-branch";
 - grafting on additional (historical) history;
 - replacing ancient broken commits with fixed ones, for use by "git
   bisect".

Hope that helps,
Jonathan

[1] http://thread.gmane.org/gmane.comp.version-control.git/6470/focus=6484
found with "git log --grep=graft --reverse"
[2] http://thread.gmane.org/gmane.comp.version-control.git/37744/focus=37908