{"id":102495,"date":"2019-05-15T07:00:00","date_gmt":"2019-05-15T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102495"},"modified":"2020-07-02T08:25:45","modified_gmt":"2020-07-02T15:25:45","slug":"20190515-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190515-00\/?p=102495","title":{"rendered":"Mundane git commit-tree tricks, Part 7: Combining more than two files into one while preserving line history, manual octopus merging"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190514-00\/?p=102493\"> Last time<\/a>, we saw how to combine two files to form a third file, while preserving line history. But what if you need to combine more than two files? For example, maybe you want to take a whole bunch of <code>csv<\/code> files and merge them into one big file, but still track the origin of each line.<\/p>\n<p>Let&#8217;s set up a scratch repo to try it out.<\/p>\n<pre>git init\r\n\r\n&gt;fruits echo apple\r\n&gt;&gt;fruits echo grape\r\n&gt;&gt;fruits echo orange\r\ngit add fruits\r\ngit commit --author=\"Alice &lt;alice&gt;\" -m \"create fruits\"\r\n\r\n&gt;veggies echo celery\r\n&gt;&gt;veggies echo lettuce\r\n&gt;&gt;veggies echo peas\r\ngit add veggies\r\ngit commit --author=\"Bob &lt;bob&gt;\" -m \"create veggies\"\r\n\r\n&gt;dairy echo cheese\r\n&gt;&gt;dairy echo eggs\r\n&gt;&gt;dairy echo milk\r\ngit add dairy\r\ngit commit --author=\"Carol &lt;carol&gt;\" -m \"create dairy\"\r\n\r\ngit tag ready\r\n<\/pre>\n<p>We can use the trick from last time to merge two files, and extend it to three files by performing an octopus merge.<\/p>\n<pre>git checkout -b d2f\r\ngit mv dairy food\r\ngit commit -m \"dairy to food\"\r\ngit checkout -\r\n\r\ngit checkout -b f2f\r\ngit mv fruits food\r\ngit commit -m \"fruits to food\"\r\ngit checkout -\r\n\r\ngit checkout -b v2f\r\ngit mv veggies food\r\ngit commit -m \"veggies to food\"\r\ngit checkout -\r\n\r\ngit merge d2f f2f v2f\r\n<\/pre>\n<p>Except that it doesn&#8217;t work. In fact, it explodes quite spectacularly.<\/p>\n<pre>Fast-forwarding to: d2f\r\nTrying simple merge with f2f\r\nSimple merge did not work, trying automatic merge.\r\nAdded food in both, but differently.\r\nfatal: unable to read blob object e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\r\nerror: Could not stat : No such file or directory\r\nERROR: content conflict in food\r\nfatal: merge program failed\r\nAutomated merge did not work.\r\n<b>Should not be doing an octopus.<\/b>\r\nMerge with strategy octopus failed.\r\n<\/pre>\n<p>I like that second-to-last line that scolds you for attempting this sort of thing in the first place.<\/p>\n<p>Let&#8217;s clean up the work that merge had left in progress. You normally would do this with a <code>git merge --abort<\/code>, but octopus merges are not abortable because they don&#8217;t record enough information to permit an abort. (This is arguably a bug in git, but it&#8217;s merely an annoyance, and not something normal people are going to encounter.)<\/p>\n<pre>git reset --hard\r\n<\/pre>\n<p>The problem is that octopus merges work only if there are no conflicts. We&#8217;re going to have to build our own octopus merge.<\/p>\n<pre>cat dairy fruits veggies | sort &gt;food\r\ngit rm dairy fruits veggies\r\ngit add food\r\ngit write-tree\r\n<\/pre>\n<p>The <code>git write-tree<\/code> creates a tree from the index. It&#8217;s the tree that a <code>git commit<\/code> would create, but we don&#8217;t want to do a normal commit. This is the tree we want to commit, but we need to set custom parents, so we&#8217;ll ask <code>git write-tree<\/code> for the tree that <i>would be committed<\/i>, so we can build our custom commit.<\/p>\n<pre style=\"white-space: pre-wrap;\">git commit-tree \u2329tree-hash\u232a -p HEAD -p d2f -p f2f -p v2f -m \"combine dairy, fruits, and veggies\"\r\n<\/pre>\n<p>The <code>commit-tree<\/code> will print another hash. This is the hash of the manually-constructed octopus merge.<\/p>\n<pre>git merge --ff-only \u2329commit-hash\u232a\r\n<\/pre>\n<p>I like to use <code>--ff-only<\/code> to make sure that I really am just moving forward.<\/p>\n<pre>git blame food\r\n\r\n^7c5ae53 fruits  (Alice 2019-05-15 07:00:00 -0700 1) apple\r\n03c4572c veggies (Bob   2019-05-15 07:00:01 -0700 2) celery\r\n65430aff dairy   (Carol 2019-05-15 07:00:02 -0700 3) cheese\r\n65430aff dairy   (Carol 2019-05-15 07:00:02 -0700 4) eggs\r\n^7c5ae53 fruits  (Alice 2019-05-15 07:00:00 -0700 5) grape\r\n03c4572c veggies (Bob   2019-05-15 07:00:01 -0700 6) lettuce\r\n65430aff dairy   (Carol 2019-05-15 07:00:02 -0700 7) milk\r\n^7c5ae53 fruits  (Alice 2019-05-15 07:00:00 -0700 8) orange\r\n03c4572c veggies (Bob   2019-05-15 07:00:01 -0700 9) peas\r\n<\/pre>\n<p>There are other ways we could have produced the same result. For example, we could have performed a series of two-files-into-one merges, but this way gives us a single commit on the trunk that captures the &#8220;combine multiple files into one&#8221;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes you just have to take things into your own hands.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[26],"class_list":["post-102495","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-other"],"acf":[],"blog_post_summary":"<p>Sometimes you just have to take things into your own hands.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102495","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=102495"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102495\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=102495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}