{"id":1250,"date":"2013-10-11T01:00:00","date_gmt":"2013-10-10T23:00:00","guid":{"rendered":"https:\/\/www.fussylogic.co.uk\/blog\/?p=1250"},"modified":"2014-03-31T10:24:02","modified_gmt":"2014-03-31T09:24:02","slug":"renaming-the-past-with-git","status":"publish","type":"post","link":"https:\/\/www.fussylogic.co.uk\/blog\/?p=1250","title":{"rendered":"Renaming The Past With Git"},"content":{"rendered":"<p>I added a directory to a repository I was working on; made some commits to the files in that directory, added some files, removed some files, then decided that I didn\u00e2\u20ac\u2122t like what I\u00e2\u20ac\u2122d named it.<\/p>\n<p>When you make a mistake like this in files with git, you can easily use <code>git rebase -i<\/code> to move a <code>fixup<\/code> commit into the past so that your mistake appears to have never happened (assuming you haven\u00e2\u20ac\u2122t pushed your branch yet). Renaming a file works okay too; git\u00e2\u20ac\u2122s pretty good at tracking that change. Renaming a whole directory, especially with adds and removals is harder.<\/p>\n<p>The solution is <code>git filter-branch<\/code>, which lets you apply alterations to all commits in a sequence of revisions. It\u00e2\u20ac\u2122s particularly versatile, but also fairly complicated to use.<\/p>\n<p>I\u00e2\u20ac\u2122m going to use the <code>--index-filter<\/code> mode of <code>git filter-branch<\/code> as it\u00e2\u20ac\u2122s fast and leaves your working directory out of it.<\/p>\n<p>First we\u00e2\u20ac\u2122ll prepare our filter; we can do this mostly non-destructively.<\/p>\n<pre><code>$ git ls-files -s\n100644 afe54f7da2cd579c8ef46abf90a2e9223e637baf 0       OLDDIR\/.gitignore\n100644 aa343b709899207effae27e9ce1b877de3ae2aec 0       OLDDIR\/Makefile\n100644 c9943eee519617204de5c156ea62266e18188b84 0       OLDDIR\/README.md<\/code><\/pre>\n<p>The filenames here are prefixed with a tab, that makes it easy to write a <code>sed<\/code> command that alters them.<\/p>\n<pre><code>$ git ls-files -s | sed &quot;s|\\tOLDIR\\(.*\\)|\\tNEWDIR\\1|&quot;\n100644 afe54f7da2cd579c8ef46abf90a2e9223e637baf 0       NEWDIR\/.gitignore\n100644 aa343b709899207effae27e9ce1b877de3ae2aec 0       NEWDIR\/Makefile\n100644 c9943eee519617204de5c156ea62266e18188b84 0       NEWDIR\/README.md<\/code><\/pre>\n<p>What we\u00e2\u20ac\u2122ve done here is made a list that gives new names to particular contents (remember that git identified content by its hash, not by its name). This list is exactly the right format to feed into <code>git update-index<\/code>.<\/p>\n<pre><code>$ git ls-files -s | sed &#39;s|\\tOLDIR\\(.*\\)|\\tNEWDIR\\1|&#39; \\\n    | git update-index --index-info<\/code><\/pre>\n<p>If you run <code>git status<\/code> after running this command you\u00e2\u20ac\u2122ll see that Git has listed all the renames as if they are new files. That\u00e2\u20ac\u2122s because we only updated the existing index, leaving all the current <code>OLDDIR\/<\/code> entries in place. What we need to do is create a new index then replace the old with it \u00e2\u20ac\u201c the new index will not include any old listings so git will see that as a rename rather than an add.<\/p>\n<pre><code>$ git ls-files -s | sed &#39;s|\\tOLDIR\\(.*\\)|\\tNEWDIR\\1|&#39; \\\n    | GIT_INDEX_FILE=.git\/tempnewindex git update-index --index-info \\\n    &amp;&amp; mv .git\/tempnewindex .git\/index<\/code><\/pre>\n<p>After this you\u00e2\u20ac\u2122ll see:<\/p>\n<pre><code>$ git status\n# On branch temp\n# Changes to be committed:\n#   (use &quot;git reset HEAD &lt;file&gt;...&quot; to unstage)\n#\n#       renamed:    OLDDIR\/.gitignore -&gt; NEWDIR\/.gitignore\n#       renamed:    OLDDIR\/Makefile -&gt; NEWDIR\/Makefile\n#       renamed:    OLDDIR\/README.md -&gt; NEWDIR\/README.md\n#\n# Changes not staged for commit:\n#   (use &quot;git add\/rm &lt;file&gt;...&quot; to update what will be committed)\n#   (use &quot;git checkout -- &lt;file&gt;...&quot; to discard changes in working directory)\n#\n#       deleted:    NEWDIR\/.gitignore\n#       deleted:    NEWDIR\/Makefile\n#       deleted:    NEWDIR\/README.md<\/code><\/pre>\n<p>The \u00e2\u20ac\u0153not staged\u00e2\u20ac\u009d deletions are because we didn\u00e2\u20ac\u2122t change the working directory. Don\u00e2\u20ac\u2122t worry about that, remember it\u00e2\u20ac\u2122s only the index that gets committed.<\/p>\n<p>We now use this command with <code>git filter-branch<\/code> to do the same thing for a series of commits. We need to make a slight modification to cope with <code>git filter-branch<\/code>\u00e2\u20ac\u2122s use of subdirectories, by using the environment variable it sets for us <code>$GIT_INDEX_FILE<\/code>.<\/p>\n<pre><code>$ git filter-branch --index-filter &#39;git ls-files -s \\\n    | sed &quot;s|\\tOLDIR\\(.*\\)|\\tNEWDIR\\1|&quot; \\\n    | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info \\\n        &amp;&amp; mv &quot;$GIT_INDEX_FILE.new&quot; &quot;$GIT_INDEX_FILE&quot;&#39; \\\n        HEAD^^^^..HEAD<\/code><\/pre>\n<p>This does the rename on the last four revisions.<\/p>\n<p>Blink and you\u00e2\u20ac\u2122ll miss it. I\u00e2\u20ac\u2122d advise doing this on a temporary branch until you\u00e2\u20ac\u2122re happy that it\u00e2\u20ac\u2122s worked then <code>git reset<\/code> your master branch to the temporary to activate it (although Git will make a backup for you, so it\u00e2\u20ac\u2122s not the end of the world if you don\u00e2\u20ac\u2122t).<\/p>\n<p>You\u00e2\u20ac\u2122ll find almost exactly this final command written on the <code>man<\/code> page for <code>git-filter-branch<\/code>; but hopefully the above will let you build up to it gradually so you can test out the change (in particular the <code>sed<\/code> command line) before doing anything.<\/p>\n<p>Be careful though \u00e2\u20ac\u201c it\u00e2\u20ac\u2122s not hard to lose any non-revision-controlled files you keep in these directories when you\u00e2\u20ac\u2122re experimenting with this.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I added a directory to a repository I was working on; made some commits to the files in that directory, added some files, removed some files, then decided that I didn\u00e2\u20ac\u2122t like what I\u00e2\u20ac\u2122d named it. When you make a mistake like this in files with git, you can easily use git rebase -i to\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.fussylogic.co.uk\/blog\/?p=1250\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[120,6],"_links":{"self":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1250"}],"collection":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1250"}],"version-history":[{"count":3,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1250\/revisions"}],"predecessor-version":[{"id":1258,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1250\/revisions\/1258"}],"wp:attachment":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1250"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1250"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1250"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}