How do git grafts and replace differ? (Are grafts now deprecated?)

Viewed 19578

There are very few Q&A's on git grafts versus replace. The search [git] +grafts +replace only found two that felt relevant of the 5. what-are-git-info-grafts-for and git-what-is-a-graftcommit-or-a-graft-id. There is also a note on git.wiki.kernel.org:GraftPoint

Are grafts now completely overtaken by the replace and filter-branch, or do they still needed for some special corner cases (and backward compatibility) ?

In general, how do they differ (e.g. which are transported between repos), and how are they generically the same? I've seen that Linus doesn't appear to care about grafts at present in the discussion on commit generation numbers (of the max parents back to any root variety) "Grafts are already unreliable."

EDIT: more info found.
A search of www.kernel.org/pub/software/scm/git/docs for graft only found 3 results:

  1. git-filter-branch(1),
  2. v1.5.4.7/git-filter-branch(1),
  3. v1.5.0.7/git-svn(1).

A slightly broader search found RelNotes/1.6.5.txt which contains:

  • refs/replace/ hierarchy is designed to be usable as a replacement of the "grafts" mechanism, with the added advantage that it can be transferred across repositories.

Unfortunately, the gitrepository-layout(5) isn't yet up to date with the refs/replace/ repository layout info (and notes), nor any deprecation note of info/grafts.

This gets closer to supporting what I was thinking but I'd welcome any confirmation or clarification.

3 Answers

If you need to rewrite a parent commit using git replace, this is how to do it.

As Philip Oakley mentioned, git replace simply replaces one commit with another. To graft on a parent to an existing commit, you need to first create a fake commit with the correct parent.

Say you have two git branchs you want to graft:

(a)-(b)-(c) (d)-(e)-(f)

Now we want (d) to be the parent of (c). So we create a replacement for (c) with the correct parent (let's call this c1), then git replace (c) with (c1). In these steps each of the letters refers to the SHA1 hash representing that commit.

To create the new commit:

git checkout d
git rm -rf * # remove all files from working direcotry
git checkout c -- . # commit everything from c over top of it
git commit -a -C c # create replacement commit with original info

Now you have commit (c1) which has the correct parent (d). So all we need to do is replace the existing (c) with (c1):

git replace c c1

Now your history looks like this:

(a)-(b)-(c1)-(d)-(e)-(f)

Bingo!

Related