How can I move a directory in a Git repo for all commits?
Solution 1
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
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
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
mipadi
Just some guy, y'know? I write code for Industrial Light & Magic. I'm writing a book on Objective-C. Or rather, I was. Thanks to Apple, I'm now writing a book on Swift. You can check it out here. Oh, I also wrote a URL shortener for Stack Overflow profiles. Feel free to use it if you want!
Updated on July 05, 2022Comments
-
mipadi almost 2 years
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 ofsome-post.html
viagit log -- _posts/some-post.html
. I imagine I can use some magic withgit filter-branch
to accomplish this, but I haven't figured out exactly how to do that. Any ideas? -
Cascabel almost 14 yearsIt's also much faster to use
--index-filter
, since it doesn't have to check out the tree. -
sehe about 13 yearsYeah index-filter is faster, but it won't work because the commands shown do not affect the index. You need to do index manipulations only if you want to use index-filter (e.g.
git rm --cached
instead ofrm
) -
Jeremy Huiskamp over 10 yearsAnd since it's not immediately obvious from looking at that command or the resulting errors, the \t doesn't work on os x's version of sed. There's lots of ways around that, but perhaps the quickest is to delete the \t and replace it with a literal tab by typing ctrl-v, <tab>.
-
joshcomley over 9 yearsHow do you specify the original folder, or does this just move the entire branch? When I try to run this from a folder I'd like to move I get
You need to run this command from the toplevel of the working tree
-
Lucius about 9 yearsWhat does the
sed
command do? I'm trying this on Windows and need an alternative. -
Will about 9 yearsThanks, this helped me a lot! @Lucius, the sed command is much more clear if you replace the "&" with another \t and the "-"s with an @.
-
KCD almost 9 yearsBrilliant. But yes the sed is confusing so try the second line alone to test it. I.e. to remove unnecessary top level directories I did a simple
git ls-files -s | sed "s-\tdir1/dir2/dir3/-\t-"
-
dr0i about 8 yearsI tried so many answers on the internet, this is the only one working - thx a lot!
-
Knut Forkalsrud about 8 yearsIf you're filtering a commit that effectively deletes all files you end up with
git update-index
not creating the file "$GIT_INDEX_FILE.new" and thus the mv command fails. I ended up withtest -f \$GIT_INDEX_FILE.new && mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE || touch \$GIT_INDEX_FILE
inside the filter-branch script. -
Admin almost 8 years@KnutForkalsrud I get the error of index.new': No such file or directory, as this git repository was a result of an svn-migration, there is no initial commit. Could you share your command, or is it just insert that into the filter, with no quotations?
-
Knut Forkalsrud almost 8 years@Jägermeister - just replace the "mv" command in theduke's example with the "test && mv || touch" combination from my comment.
-
polm23 almost 7 yearsSomething about using
-
as a separator confused my sed, so I had to change that like so:sed "s:\t\"*:&NEWSUBDIR/:"
. -
briceburg almost 7 yearsAs noted, BSD/macos sed does not support
\t
, and the pattern fails on paths with hyphens in their name -- so I wrote a cross platform solution that uses awk;git ls-files -s | awk '{ sub(/\t/, "\tsubdir/"); print}'
full usage: set DEST to desired subdirDEST="subdir/path/" git filter-branch -f --index-filter 'git ls-files -s | awk -v prefix="$DEST/" "{ sub(/\t/, \"\t\" prefix); print}" GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD
-
Michael almost 7 yearsCan I keep tags too? It looks like they are gone in my case.
-
jthill over 6 years
git filter-branch --index-filter 'git read-tree --prefix=/ $GIT_COMMIT:_posts; git rm -r --cached _posts' -- --all
. Add--tag-name-filter cat
if your tags aren't signed and you want to move them/invalidate the old ones. -
gablin about 6 years@briceburg The
mv
is missing between&&
and"$GIT_INDEX_FILE.new"
. Other than that, it works perfectly! -
askb over 5 yearsFails with the following error Rewrite 7576b38152b393793b1c9ec3df0ff86685f95236 (1/8246) (0 seconds passed, remaining 0 predicted) mv: cannot stat '/tmp/controller/.git-rewrite/t/../index.new': No such file or directory index filter failed: git ls-files -s | sed "s-\t\"*-&opendaylight/blueprint/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
-
Roi Danton over 5 years@askb: You can skip bad commits with appending
; /bin/true
. See this excellent post. -
SiliconMind over 4 yearsFor those that are here just for copy-paste: remember to
git push --force --all
for all the branches. Otherwise you can end up with funny situations. -
Etienne Maheu about 4 yearsFor those Windows users who stumbled on this answers like me and wonder how to make it work on Windows: consider using WSLS and run the command from linux. Just make sure that your repository is checked out with AutoCRLF to false or else the git command in linux is going to think all files have changes and will block the execution.
-
AVIDeveloper about 4 yearsRunning under Cygwin on Windows, the above didn't work. Eventually, my issue was that my subdir contains dashes (e.g. "sect-03"), which is the separator used in the
sed
section. Replacingsed "s-\t\"*-§-03/-"
withsed "s/\t\"*/§-03\//"
did the trick.