git notes main page | gitolite main page | license

IMPORTANT NOTE: although this page has a "gitolite.com" URL, this is not about gitolite. That's just an artifact of "sitaramc.github.com" being translated to "gitolite.com" and so ALL my git related stuff gets carried over. Gitolite documentation has another /gitolite in the URL, so you can tell. My apologies for this confusion.

gardening tips – grafting branches

[If you think the jokes are bad, you don’t know me very well. Clearly that is your fault, not mine :-)]


Even people who consider themselves really good gardeners shy away from grafting. Wikipedia says “For successful grafting to take place, the vascular cambium tissues of the stock and scion plants must be placed in contact with each other. Both tissues must be kept alive until the graft has taken, usually a period of a few weeks.” That sounds… difficult.

Fortunately, Grum on IRC explained to me that all that is bunkum in git. Git has no issues with tissues, and if Linus had to wait a few weeks for a graft to “take”, we’d still be at kernel level 2.2 or something.

So, here’s the long and short of grafting in git. Enjoy! (And thank Grum with a ++ next time you see him on #git!).

Note: The following description will use the word “line” to mean “a long chain of commits with no branch or merge within it”. In gitk --all, it is literally a straight line.

1 connecting two unrelated lines

Let’s say you have the following history, with two unrelated lines of development (quite easily achieved by just fetching from some other repo that has no common commits with yours!).

^^(By the way, how can you tell they are unrelated? Easy – just look at the break in the graph connector after “a1”!)^^

$ git log --graph '--pretty=tformat:%H %d %s %n'  --all
* 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  (refs/heads/a-m) a3
|
* 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2
|
* bb1cd6894bf5a073a80fb67575adb504539fca86  a1

* b2ccb766b20061124010877b5c22e69015e0d076  (refs/heads/master) b3
|
* adec8003a1c14984a8c39fc796c1ccbac27048e0  b2
|
* c5e268659a39921d87d6d50d6d3c6eccd12f4916  b1

Suppose the two lines are actually of the same project, and you now want to join them. You have decided that “b1” is the true root of the repository (probably because that is the one that a couple of thousand people have already cloned, and you can’t change that chain ;-) and want to make the “a” line a fork from the “b” line at the point “b1”.

Just make a graft that says that “b1” is the parent of “a1”! Of course you have to use the full SHA1s, and this isn’t one of the more user-friendly parts of git, but:

$ echo bb1cd6894bf5a073a80fb67575adb504539fca86 \
c5e268659a39921d87d6d50d6d3c6eccd12f4916 > .git/info/grafts

So now the repo looks like this:

$ git log --graph '--pretty=tformat:%H %d %s %n' --all
* 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  (refs/heads/a-m) a3
|
* 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2
|
* bb1cd6894bf5a073a80fb67575adb504539fca86  a1
|
| * b2ccb766b20061124010877b5c22e69015e0d076  (refs/heads/master) b3
| |
| * adec8003a1c14984a8c39fc796c1ccbac27048e0  b2
|/
|
* c5e268659a39921d87d6d50d6d3c6eccd12f4916  b1

So far so good. You’ve somehow made “a1” a child of “b1”, but there’s something not quite right – the adoption has merely been recorded on a wee bit of notepaper, called .git/info/grafts. That thing could get lost, damaged, or whatever. In fact, it won’t even make it past a clone, leave alone pushes and pulls. You need to get a more formal adoption certificate, on thick, strong, bond paper. With the logo of the local something-or-other. Maybe even laminated!

Here’s how to apply for one from the district magistrate’s office:

git filter-branch --tag-name-filter cat -- --all

And now the repo looks like this:

$ git log --graph '--pretty=tformat:%H %d %s %n' master a-m
* b2ccb766b20061124010877b5c22e69015e0d076  (refs/heads/master) b3
|
* adec8003a1c14984a8c39fc796c1ccbac27048e0  b2
|
| * f34c278dd638a8ade860aafb47bd7a5c8d79865b  (refs/heads/a-m) a3
| |
| * 92a7be0c15b242c8204ba86a4b5191d1492b1dd4  a2
| |
| * 941e144b19e7b0fadbd3bedc295d4e1f2441ed79  a1
|/
|
* c5e268659a39921d87d6d50d6d3c6eccd12f4916  b1

Observant people will note that the SHA1 hashes for all the “a” commits have changed, which of course they will. The SHA1 hash for a commit is derived from all parent commits, the current tree for that commit, the commit message, author/committor info, and the date/time. Since the filter-branch command has made the relationship permanent, the SHAs for the “a”s change.

Even more observant people will notice that in the previous diagram, the “a”s are in a straight line and the “b”s are on a “branch”, while in the next diagram they have switched places. Topologically that’s irrelevant, so don’t worry about it.

^^ The most observant people will notice that instead of --all I have now passed master a-m (the actual branch names). This is what you will see if you do this with --all:

$ g log --graph '--pretty=tformat:%H %d %s %n'  --all
* f34c278dd638a8ade860aafb47bd7a5c8d79865b  (refs/heads/a-m) a3
|
* 92a7be0c15b242c8204ba86a4b5191d1492b1dd4  a2
|
* 941e144b19e7b0fadbd3bedc295d4e1f2441ed79  a1
|
| * b2ccb766b20061124010877b5c22e69015e0d076  (refs/heads/master) b3
| |
| * adec8003a1c14984a8c39fc796c1ccbac27048e0  b2
|/
|
| * 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  (refs/original/refs/heads/a-m) a3
| |
| * 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2
| |
| * bb1cd6894bf5a073a80fb67575adb504539fca86  a1
|/
|
* c5e268659a39921d87d6d50d6d3c6eccd12f4916  b1

That’s an artifact of the filter-branch command and git’s well-known principle of making it very hard to lose data. If you did not like the filter-branch for some reason, you would just:

git checkout a-m
git reset --hard original/refs/heads/a-m

But if everything worked out, and the new tree looks good, you would type, very carefully:

rm -rf .git/refs/original/

That’s it! ^^

2 breaking a line into two…

What other tricks can you get up to?

How about taking this:

$ g log --graph '--pretty=tformat:%H %d %s %n'  --all
* cb479e433d0fa639c5738f9b26c0cd5ca0d1e1e7  (refs/heads/master) a9
|
* 0f127260fff51ad71d092788d5630b6bf0c4764f  a8
|
* fb9c6ac521e99216d0202d54d03800e2ebd1d3a4  a7
|
* 8e48f4975469c9c9c5de644eb8eff1e165241b09  a6
|
* 6071135a250f9870aa8211eb871b4ac5d8e7e585  (refs/heads/branch) a5
|
* 179fb20ebbe3d9381b8944cc53c1ef0b893f431d  a4
|
* 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  a3
|
* 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2
|
* bb1cd6894bf5a073a80fb67575adb504539fca86  a1

and turning it into this:

$ g log --graph '--pretty=tformat:%H %d %s %n'  --all
* cb479e433d0fa639c5738f9b26c0cd5ca0d1e1e7  (refs/heads/master) a9 at Sat Apr 11 17:20:21 IST 2009
|
* 0f127260fff51ad71d092788d5630b6bf0c4764f  a8 at Sat Apr 11 17:20:19 IST 2009
|
* fb9c6ac521e99216d0202d54d03800e2ebd1d3a4  a7 at Sat Apr 11 17:20:17 IST 2009
|
* 8e48f4975469c9c9c5de644eb8eff1e165241b09  a6 at Sat Apr 11 17:20:15 IST 2009

* 6071135a250f9870aa8211eb871b4ac5d8e7e585  (refs/heads/branch) a5 at Sat Apr 11 17:20:13 IST 2009
|
* 179fb20ebbe3d9381b8944cc53c1ef0b893f431d  a4 at Sat Apr 11 17:20:11 IST 2009
|
* 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  a3 at Sat Apr 11 16:21:44 IST 2009
|
* 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2 at Sat Apr 11 16:21:42 IST 2009
|
* bb1cd6894bf5a073a80fb67575adb504539fca86  a1 at Sat Apr 11 16:21:40 IST 2009

How? Just:

$ echo 8e48f4975469c9c9c5de644eb8eff1e165241b09 > .git/info/grafts

We’ve made “a6” what is called a “root” commit – it has no parent. Of course, if you do this without giving “a5” (“a6”’s original parent) a proper name, “a5” will become unreachable. Which is why we gave it a name.

3 …and joining them parallely (branch + merge)

Or how about not just breaking them and leaving them separate, but taking the pieces and putting them parallel to each other, with the tops and bottoms joined:

$ echo 6071135a250f9870aa8211eb871b4ac5d8e7e585 \
bb1cd6894bf5a073a80fb67575adb504539fca86 > .git/info/grafts

$ echo cb479e433d0fa639c5738f9b26c0cd5ca0d1e1e7 \
0f127260fff51ad71d092788d5630b6bf0c4764f \
179fb20ebbe3d9381b8944cc53c1ef0b893f431d >> .git/info/grafts

$ g log --graph '--pretty=tformat:%H %d %s %n'  --all
*   cb479e433d0fa639c5738f9b26c0cd5ca0d1e1e7  (refs/heads/master) a9
|\
| |
| * 179fb20ebbe3d9381b8944cc53c1ef0b893f431d  a4
| |
| * 891f2a69544779e7f61a6ae2c213a3c2dc2957b1  a3
| |
| * 6a5b7c3ba50f91630f71aeaa36e2926ea6ad42f4  a2
| |
* | 0f127260fff51ad71d092788d5630b6bf0c4764f  a8
| |
* | fb9c6ac521e99216d0202d54d03800e2ebd1d3a4  a7
| |
* | 8e48f4975469c9c9c5de644eb8eff1e165241b09  a6
| |
* | 6071135a250f9870aa8211eb871b4ac5d8e7e585  a5
|/
|
* bb1cd6894bf5a073a80fb67575adb504539fca86  a1

They say gardening is the closest to playing God that God-fearing folks dare to do :-) Feel the power yet?