61

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.

Community
  • 1
  • 1
Philip Oakley
  • 13,333
  • 9
  • 48
  • 71
  • Yes, it looks that grafts are becoming [deprecated](https://stackoverflow.com/a/50517809/350384) and `replace` can do everything that grafts were used for. – Mariusz Pawelski Jan 03 '19 at 15:22

3 Answers3

20

In the same discussion about Commit Generation Number that you mention, Jakub Narębski does confirm that grafts are more aproblem than a solution:

grafts are so horrible hack that I would be not against turning off generation numbers if they are used.
In the case of replace objects you need both non-replaced and replaced DAG generation numbers.
[...] Grafts are non-transferable, and if you use them to cull rather than add history they are unsafe against garbage collection... I think.

(publishing has always been taken care of with git filter-branch, as illustrated by this 2008 thread on grafts workflow.)

The difference between grafts and git replace is best illustrated by this SO question "Setting git parent pointer to a different parent", and the comments of (Jakub's again) answer.

It does include the reference to Git1.6.5

From what I understand (from GraftPoints), git replace has superseded git grafts (assuming you have git 1.6.5 or later)

(Jakub:)

  • if you want to rewrite history then grafts + git-filter-branch (or interactive rebase, or fast-export + e.g. reposurgeon) is the way to do it.
  • If you want/need to preserve history, then git-replace is far superior to graft
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 12
    so grafts defines an alternate parentage for an existing commit, while replace defines an alternate commit (Object) to replace an existing commit (Object), but you have to create that replacement object whilst a graft does not require its creation. However, replace refs are copied between repos, while grafts aren't (I think). – Philip Oakley Jul 23 '11 at 17:52
  • 1
    @Philip: yes, I believe that sums up the difference between the two. – VonC Jul 23 '11 at 18:07
  • Jacub's comment was in response to 'amend the history DAG .. using grafts *and* replace objects' - I suspect he was referring to both. So, for the commit generation number discussion, they both could be problematic (unless a way is found using the transported refs/replace, but I doubt it [embedded generation numbers] are worth it; Recreate them as part of the local index/repo, but don't transport them.) – Philip Oakley Jul 23 '11 at 19:46
  • 1
    :@VonC: All we need now is a graft2replace script that will take a line from the grafts file and create the missing commit & stuff the link in to refs/replace and were done ;-) – Philip Oakley Jul 24 '11 at 20:49
  • @Philip: yes, provided that we are sure all those grafts were actually intended to be published between repo as full-fledge commits (as opposed to private fake commit ancestry informations) – VonC Jul 24 '11 at 21:21
  • Yes, I was thinking of it as a local helper function for the user in just that case [of wanting to make a local graft, into a published history update]. i.e. it would have to be user initiated with that goal in mind. – Philip Oakley Jul 25 '11 at 06:56
  • @Philip: local helper: agreed. – VonC Jul 25 '11 at 07:04
14

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!

Matthias Urlichs
  • 2,301
  • 19
  • 29
Adrian Macneil
  • 13,017
  • 5
  • 57
  • 70
  • Nice guide! Only thing I missed was the date format, which I had to google for. Apparently git accepts dates in RFC 2822 (`Thu, 07 Apr 2005 22:13:13 +0200`) and ISO 8601 (`2005-04-07T22:13:13`). – Markus T Jul 08 '14 at 22:57
  • 2
    How is d the parent of c in the final graph? Surely c (now c1) is the parent of d? Shouldn't it be a-b-d-c1-e-f? – Will Dean Feb 03 '16 at 09:11
  • `git replace --graft ` is the simplest way – caot Jan 15 '19 at 20:02
8

EDIT: git replace --graft <commit> [<parent>…​] does the same thing as grafts and it can add or remove parents. The documentation says:

Create a graft commit. A new commit is created with the same content as <commit> except that its parents will be [<parent> …​] instead of <commit>'s parents. A replacement ref is then created to replace with the newly created commit.

(I'm leaving the old answer below as a reference.)


AFAIK, there is one use case that grafts can handle but replace cannot: adding or removing parents. It's a power tool for refactoring histories.

For example, if you're importing the history from an old SVN repository into Git, there is no merge information. What you can do (and I've done it lots of times) is to read through the commit messages to find out where a SVN "merge" was done, and then use Git grafts to add a parent to the merge commit.

IIRC, I've also had some cases where I've removed the parent of a commit, in order to make it the first commit in the history. Creating a clean history based on multiple chaotic legacy repositories sometimes requires drastic measures (there are some experiences of migrating projects to Git at my blog).

Then after you've cleaned up the whole history, you would do a git filter-branch before publishing the new Git repository.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
  • 1
    I thought that the `git replace [-f] ` allowed multiple replacements in the same way that grafts does, but I (google) haven't found anything that confirms that. – Philip Oakley Aug 03 '13 at 12:00
  • 1
    Just read the code (also at https://github.com/git/git/blob/master/builtin/replace.c) and it does look like it only allows a single replacement. However that replacement [commit] object could be a merge itself, though you have to fake that up yourself first. – Philip Oakley Aug 03 '13 at 12:48
  • 5
    The key correction is that `git replace` CAN do all the actions of grafts, including adding or removing parents from a commit object. Simply replace the old object with a new commit object with the correct parents. Merge commits and single parent commits are both of the same object type so can be interchanged freely by `replace`. Hopefully the documentation can be expanded to make this clear. Would you be able to correct your response? – Philip Oakley Sep 01 '13 at 19:42
  • Why is `git replace --graft ...` not good enough? – Raedwald Feb 22 '17 at 16:10