How can I move a directory in a Git repo for all commits?

GitGit Filter-Branch

Git Problem Overview


Let's say I have a repo that includes this directory structure:

repo/
  blog/
    _posts/
      some-post.html
  another-file.txt

I want to move _posts to the top level of the repo, so the structure will look like this:

repo/
  _posts/
    some-post.html
  another-file.txt

This is simple enough with git mv, but I want to make the history look as though _posts always existed at the root of the repo, and I want to be able to get the entire history of some-post.html via git log -- _posts/some-post.html. I imagine I can use some magic with git filter-branch to accomplish this, but I haven't figured out exactly how to do that. Any ideas?

Git Solutions


Solution 1 - Git

You can use the subdirectory filter to achieve this

 $ git filter-branch --subdirectory-filter blog/ -- --all

EDIT 1: If you don't want to effectively make _posts the root, use a tree-filter instead:

 $ git filter-branch --tree-filter 'mv blog/_posts .' HEAD

EDIT 2: If blog/_posts did not exist in some of the commits, the above will fail. Use this instead:

 $ git filter-branch --tree-filter 'test -d blog/_posts && mv blog/_posts . || echo "Nothing to do"' HEAD

Solution 2 - Git

While Ramkumar's answer is very helpful and worthwile, it will not work in many situations. For example, when you want to move a directory with other subdirectories to a new location.

For this, the man page contains the perfect command:

git filter-branch --index-filter \
  'git ls-files -s | sed "s-\t\"*-&NEWSUBDIR/-" |
   GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
   git update-index --index-info &&
   mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD

Just replace NEWSUBDIR with your desired new directory. You can also use nested dirs like dir1/dir2/dir3/-"

Solution 3 - Git

Created a generic script for arbitrary moves/renames: https://gist.github.com/xkr47/f766f4082112c086af63ef8d378c4304

Examples:

git filter-mv 's!^!subdir/!'

➜ moves all files to a subdirectory "subdir/" in all commits of the current branch

git filter-mv 's!^foo/bar.txt$!foo/barbar.txt!'

➜ renames foo/bar.txt to foo/barbar.txt in all commits of the current branch

Solution 4 - Git

git filter-branch is discouraged. You can use git filter-repo is much more convenient:

git filter-repo --path-rename blog/_posts:_posts`

See here.

Also note that installation somehow is broken, and you have to install from pip and add git-filter-repo to $PATH

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmipadiView Question on Stackoverflow
Solution 1 - GitartagnonView Answer on Stackoverflow
Solution 2 - GitthedukeView Answer on Stackoverflow
Solution 3 - GitJonas BerlinView Answer on Stackoverflow
Solution 4 - GitConstantineView Answer on Stackoverflow