{"id":102904,"date":"2019-09-19T07:00:00","date_gmt":"2019-09-19T14:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=102904"},"modified":"2019-09-19T10:41:32","modified_gmt":"2019-09-19T17:41:32","slug":"20190919-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190919-00\/?p=102904","title":{"rendered":"How to duplicate a file while preserving git line history"},"content":{"rendered":"<p>Today, we&#8217;re going to duplicate a file while preserving git line history.<\/p>\n<p>This could be useful if you want two copies of a component, say, one where you are doing a bunch of disruptive work, and another that remains largely unchanged. The project continues to use the old, stable version, but there&#8217;s a feature flag to switch to the new, exciting one. Eventually, you&#8217;ll make the new, exciting one the default version.<\/p>\n<p>When you do this, you want the line history of the new version to be the same as the line history of the old version, because the new version is basically a fork of the old version.<\/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>Let&#8217;s set up a scratch repo to demonstrate. I&#8217;ve omitted the command prompts so you can copy-paste this into your shell of choice and play along at home. (The timestamps and commit hashes will naturally be different.)<\/p>\n<pre style=\"white-space: pre-wrap;\">git init\r\n\r\n&gt;foods echo apple\r\ngit add foods\r\ngit commit --author=\"Alice &lt;alice&gt;\" -m created\r\n\r\n&gt;&gt;foods echo orange\r\ngit commit --author=\"Bob &lt;bob&gt;\"    -am orange\r\n\r\ngit blame foods\r\n\r\n^62ef37c (Alice 2019-09-19 07:00:00 -0700 1) apple\r\n335acb1b (Bob   2019-09-19 07:00:01 -0700 2) orange\r\n<\/pre>\n<p>We employ our standard trick: Create a branch where the desired new file appears to have been created via a rename of the original file. And then restore the original file.<\/p>\n<pre style=\"white-space: pre-wrap;\">git checkout -b dup\r\n\r\ngit mv foods foods-new\r\ngit commit --author=\"Greg &lt;greg&gt;\" -m \"duplicate foods to foods-new\"\r\n\r\ngit checkout HEAD~ foods\r\ngit commit --author=\"Greg &lt;greg&gt;\" -m \"restore foods\"\r\n\r\ngit checkout -\r\n<\/pre>\n<p>On this branch, we renamed <code>foods<\/code> to <code>foods-new<\/code>. When git traces the history of the <code>foods-new<\/code> file, it&#8217;ll see that the file was created via rename from <code>foods<\/code>, so git will use <code>food<\/code>&#8216;s history to build the line history.<\/p>\n<p>And then we bring back the original <code>foods<\/code> file. We use the <code>git checkout HEAD~ foods<\/code> command to restore the file from a specific commit, namely the commit before we renamed it away.<\/p>\n<pre style=\"white-space: pre-wrap;\">git merge --no-ff dup\r\n\r\nMerge made by the 'recursive' strategy.\r\n foods-new | 2 ++\r\n 1 file changed, 2 insertions(+)\r\n create mode 100644 foods-new\r\n<\/pre>\n<p>The <code>dup<\/code> branch deleted the <code>foods<\/code> file, and then restored it. That means there was no net change to the file in the <code>dup<\/code> branch, and even <code>git log<\/code> won&#8217;t notice it by default. If you do a log of the <code>foods<\/code> file, the merge doesn&#8217;t even show up.<\/p>\n<pre style=\"white-space: pre-wrap;\">git log --oneline foods\r\n\r\n                \u2190 the merge doesn't appear\r\n335acb1 orange\r\n62ef37c created\r\n<\/pre>\n<p>The line histories of the two files are identical, because the <code>foods-new<\/code> was created at the same time an identical <code>foods<\/code> file disappeared, which made git consider the operation to be a rename for the purpose of history tracking.<\/p>\n<pre style=\"white-space: pre-wrap;\">git blame foods\r\n\r\n^62ef37c (Alice 2019-09-19 07:00:00 -0700 1) apple\r\n335acb1b (Bob   2019-09-19 07:00:01 -0700 2) orange\r\n\r\ngit blame foods-new\r\n\r\n^62ef37c foods (Alice 2019-09-19 07:00:00 -0700 1) apple\r\n335acb1b foods (Bob   2019-09-19 07:00:01 -0700 2) orange\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Synthesize it in a branch via a rename.<\/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":[25],"class_list":["post-102904","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Synthesize it in a branch via a rename.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102904","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=102904"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/102904\/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=102904"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=102904"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=102904"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}