10

So, I am trying to rename a folder in my repository using git filter-branch --index-filter, but I always end up with a "fatal: bad source" error.

The problem is easily demonstrated on a test repository using a file instead of a folder.

Preparation:

$ git init
$ echo whatever >> my_file.txt
$ git add .
$ git commit -m "initial"
$ echo whatever2 >> my_file2.txt
$ git add .
$ git commit -m "second"

Now, I am trying to change my_file.txt to your_file.txt:

$ git filter-branch --index-filter 'git mv my_file.txt your_file.txt' HEAD

But it doesn't work:

Rewrite dac9a2023bdf9dd0159fab46213d9e1342ae9f75 (1/2)fatal: bad source, source=my_file.txt, destination=your_file.txt
index filter failed: git mv my_file.txt your_file.txt

However, the very same git mv command executed normally works without problems:

$ git mv my_file.txt your_file.txt
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    my_file.txt -> your_file.txt
#

I am sure, I am missing something essential here - but what is it?

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 1
    My guess is that in that particular commit, `my_file.txt` doesn't exist, which causes `git mv` to fail - you might try `git mv -k -f` instead. Also, `git mv` is not really appropriate for an `--index-filter` since it affects more than just the index - that should probably be a `--tree-filter` instead. – twalberg Feb 22 '13 at 17:07
  • @twalberg: Your guess is not correct. `git rm [...] my_file.txt` works without problems in the --index-filter... – Daniel Hilgarth Feb 22 '13 at 17:08
  • @twalberg: But I guess the second part of your comment is the "answer": It simply doesn't work. – Daniel Hilgarth Feb 22 '13 at 17:09
  • `--index-filter` does not check out the tree for each commit, which gives it better efficiency, but, since it basically leaves the working directory alone, I don't think `git mv` is going to consistently do what you expect. It may "work", but it may give you unexpected results... – twalberg Feb 22 '13 at 17:10
  • @twalberg: Thanks for your comments. It makes sense, I didn't really think about what `git mv` does. All samples of `git rm` always use the `--cached` option to only work on the index. I found an answer based on the man page, though. – Daniel Hilgarth Feb 22 '13 at 17:16

2 Answers2

15

As twalberg points out in his answer, git mv doesn't just access the index, it also access the disk. That's probably the reason why it doesn't work.

I didn't want to use the slow --tree-filter so I tried to change the sample from the git filter-branch man page that shows how to move the complete repository into a subfolder.

The result is this - and it actually works ;-)

git filter-branch --index-filter '
git ls-files -s | \
sed "s-\(\t\"*\)my_file.txt-\1your_file.txt-" | \
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && \
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 2
    For a more detailed explanation of the above script, see this blog post: [Renaming The Past With Git](http://www.fussylogic.co.uk/blog/?p=1250). –  May 24 '14 at 05:37
  • 3
    I used your answer to [help someone who was also working on a renaming problem](http://stackoverflow.com/a/23842072/456814), along with a detailed explanation. Thanks for the tip `;)` –  May 24 '14 at 06:45
4

git mv is not really appropriate in an --index-filter clause. Since --index-filter does not check out each commit it's rewriting into the working directory, and git mv operates on the working directory (in addition to the index), using git mv in --index-filter will not accomplish what is expected. Use a --tree-filter instead. (It might be possible to accomplish this still with --index-filter by using git update-index instead, but I don't have that available off the top of my head).

twalberg
  • 59,951
  • 11
  • 89
  • 84
  • Thanks for your answer. `update-index` is what I am using. Honestly, I don't really know how it is working, I just took the sample from the man-page and tweaked the regular expression passed to `sed` :) – Daniel Hilgarth Feb 22 '13 at 17:20
  • 1
    `--tree-filter` is not really an option for me, because my real world scenario is to fix different casings of the same directory. – Daniel Hilgarth Feb 22 '13 at 17:22
  • Ouch. In that case, maybe you ought to consider doing the work on a Windows box (ugh, can't believe I just said that...) where the different casings will not cause as much pain in the event you do need to check them out for some reason (`--tree-filter` or otherwise)... – twalberg Feb 22 '13 at 17:27
  • I am working on a windows box all along. :) That's the reason why `--tree-filter` is a bad idea. I would have to do `mv CASE case_tmp && mv case_tmp case` with `CASE` being the wrong case and `case` the correct. – Daniel Hilgarth Feb 22 '13 at 17:32
  • 1
    I am actually quite happy with the `update-index` solution I found. It's fast and painless. – Daniel Hilgarth Feb 22 '13 at 17:35