{"id":102901,"date":"2019-09-18T07:00:00","date_gmt":"2019-09-18T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102901"},"modified":"2019-09-17T18:05:36","modified_gmt":"2019-09-18T01:05:36","slug":"20190918-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190918-00\/?p=102901","title":{"rendered":"How to split out pieces of a file while preserving git line history: The easy way with misleading commits"},"content":{"rendered":"<p><a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190917-00\/?p=102894\"> Last time<\/a>, we split pieces of a file into separate files while preserving line history. We had to do some <code>git commit-tree<\/code> magic to get the results we wanted. But there&#8217;s a way to do this with an octopus merge. You just have to make sure to keep the octopus happy.<\/p>\n<p>Again, let&#8217;s use the same scratch repo as we did for the last few days. You can follow the same copy\/paste script, or you can take your existing scratch repo and <code>git reset --hard ready<\/code> to get it back into its &#8220;ready to start experimenting&#8221; state.<\/p>\n<p>To do things the easy way, we create a branch for each file we want to split out.<\/p>\n<pre style=\"white-space: pre-wrap;\">git checkout -b 2f\r\n\r\ngit mv foods fruits\r\ngit commit --author=\"Greg &lt;greg&gt;\" -m \"create fruits from foods\"\r\n<\/pre>\n<p>As before, we start by renaming <code>foods<\/code> to <code>fruits<\/code>. This ensures that when git traces the history of the <code>fruits<\/code> file, it will follow the history back into the <code>foods<\/code> file.<\/p>\n<p>Next, we edit the <code>fruits<\/code> file so that it contains the lines we want to split out from <code>foods<\/code> (so far so good), but we also regenerate the <code>foods<\/code> file with only its <i>final contents<\/i>. We intend to delete the vegetables from the <code>foods<\/code> file, so we&#8217;ll delete <i>both<\/i> the fruits <i>and<\/i> the vegetables. and the rest go back into the <code>foods<\/code> file.<\/p>\n<pre style=\"white-space: pre-wrap;\">&gt;foods echo cheese\r\n&gt;&gt;foods echo eggs\r\n&gt;&gt;foods echo milk\r\ngit add foods\r\n\r\n&gt;fruits echo apple\r\n&gt;&gt;fruits echo grape\r\n&gt;&gt;fruits echo orange\r\n\r\ngit commit --author=\"Greg &lt;greg&gt;\" -am \"split fruits from foods\"\r\n\r\ngit checkout -\r\n<\/pre>\n<p>This is <i>completely misleading<\/i> and looks like we&#8217;ve lost our minds. We are ostensibly splitting the fruits out from the foods, but we also <i>threw away the veggies<\/i>. Somebody looking at this commit in isolation will say, &#8220;Hey, what happened to all the veggies?&#8221;<\/p>\n<p>But let&#8217;s keep going. Repeat the exercise by splitting out the veggies and throwing away the fruit.<\/p>\n<pre style=\"white-space: pre-wrap;\">git checkout -b 2v\r\n\r\ngit mv foods veggies\r\ngit commit --author=\"Greg &lt;greg&gt;\" -m \"create veggies from foods\"\r\n\r\ngit checkout 2f foods\r\n\r\n&gt;veggies echo celery\r\n&gt;&gt;veggies echo lettuce\r\n&gt;&gt;veggies echo peas\r\n\r\ngit commit --author=\"Greg &lt;greg&gt;\" -am \"split veggies from foods\"\r\n\r\ngit checkout -\r\n<\/pre>\n<p>To save ourselves some typing, we used a <code>git checkout 2f foods<\/code> to say &#8220;Just give me the copy of <code>foods<\/code> from the <code>2f<\/code> branch.&#8221;<\/p>\n<p>Finally, on the main branch, we also edit the <code>foods<\/code> file into its final form.<\/p>\n<pre style=\"white-space: pre-wrap;\">git checkout 2f foods\r\ngit commit --author=\"Greg &lt;greg&gt;\" -am \"split fruits and veggies from foods\"\r\n<\/pre>\n<p>This commit is also absurdly misleading because most of the contents of the <code>foods<\/code> file simply vanished!<\/p>\n<pre style=\"white-space: pre-wrap;\">git merge 2f 2v\r\n\r\nTrying simple merge with 2f\r\nTrying simple merge with 2v\r\nMerge made by the 'octopus' strategy.\r\n fruits  | 3 +++\r\n veggies | 3 +++\r\n 2 files changed, 6 insertions(+)\r\n create mode 100644 fruits\r\n create mode 100644 veggies\r\n<\/pre>\n<p>The result is now that all three files are at their desired final forms, with the desired final line attributions.<\/p>\n<pre style=\"white-space: pre-wrap;\">git blame fruits\r\n\r\n^e7a114d foods (Alice 2019-09-16 07:00:00 -0700 1) apple\r\n86348be4 foods (Bob   2019-09-16 07:00:01 -0700 2) grape\r\n34eb5bd1 foods (Carol 2019-09-16 07:00:02 -0700 3) orange\r\n\r\ngit blame veggies\r\n\r\n^e7a114d foods (Alice 2019-09-16 07:00:00 -0700 1) celery\r\n86348be4 foods (Bob   2019-09-16 07:00:01 -0700 2) lettuce\r\n34eb5bd1 foods (Carol 2019-09-16 07:00:02 -0700 3) peas\r\n\r\ngit blame foods\r\n\r\n^e7a114d (Alice 2019-09-16 07:00:00 -0700 1) cheese\r\n86348be4 (Bob   2019-09-16 07:00:01 -0700 2) eggs\r\n34eb5bd1 (Carol 2019-09-16 07:00:02 -0700 3) milk\r\n<\/pre>\n<p>However, the way we got there is very strange, and includes quite a few <i>extremely misleading<\/i> commits. I don&#8217;t really recommend it. I recommend doing it the hard way with <code>git commit-tree<\/code>. Yes, it&#8217;s harder, but it&#8217;s also much less misleading to people who come to the repo later.<\/p>\n<p><b>Bonus chatter<\/b>: The misleading commit on the main branch is necessary because of another bug in octopus merges: It silently ignores the <code>--no-ff<\/code> flag. You can see this if you skip the extra commit on the main branch and try an octopus merge right away:<\/p>\n<pre style=\"white-space: pre-wrap;\">git merge --no-ff 2f 2v\r\n\r\n<b>Fast-forwarding to: 2f<\/b>\r\nTrying simple merge with 2v\r\nMerge made by the 'octopus' strategy.\r\n foods   | 6 ------\r\n fruits  | 3 +++\r\n veggies | 3 +++\r\n 3 files changed, 6 insertions(+), 6 deletions(-)\r\n create mode 100644 fruits\r\n create mode 100644 veggies\r\n<\/pre>\n<p>Even though we said <code>--no-ff<\/code>, the octopus merge fast-forwarded anyway. The result is that the <code>foods<\/code> file failed to preserve line history.<\/p>\n<pre style=\"white-space: pre-wrap;\">git blame foods\r\n\r\n61bca29b (Greg 2019-09-18 07:00:00 -0700 1) cheese\r\n61bca29b (Greg 2019-09-18 07:00:00 -0700 2) eggs\r\n61bca29b (Greg 2019-09-18 07:00:00 -0700 3) milk\r\n<\/pre>\n<p>All the lines got blamed on Greg, when they really should be blamed on Alice, Bob, and Carol.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Anticipating the octopus.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[26],"class_list":["post-102901","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-other"],"acf":[],"blog_post_summary":"<p>Anticipating the octopus.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102901","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=102901"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102901\/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=102901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}