16

Questions about renaming files in Git have been asked before, but I can't work out a solution to my specific problem.

I have moved and edited multiple files (I didn't use git mv - unfortunately it's now too late for that). Now I want it so when my colleague pulls from my repository, having made his own edits to those same files (without moving them), it successfully merges my changes with his in the file's new location. To successfully merge, Git clearly needs to know that these are the same files.

Is Git clever enough to work this out on its own? It seems hard to believe. And if so, how can I be sure that a particular file move will be picked up by Git - even if the contents has changed?

Community
  • 1
  • 1
Robin Winslow
  • 10,908
  • 8
  • 62
  • 91
  • 2
    This is the purpose of `git mv`. – ezod Oct 08 '12 at 15:55
  • Okay but how do I tell git that a file *has* moved? I moved the files in Visual Studio, now I want to make sure git knows. I tried `git mv --cached` but that doesn't exist. When I try `git mv` I get `fatal: bad source, source= ...` – Robin Winslow Oct 08 '12 at 15:57
  • If you haven't yet committed, you could just move the files back to their original paths, and then `git mv` them to the new paths. – ezod Oct 08 '12 at 16:02
  • Okay I can do that, but it's annoying because I have to recreate whole directory structures. It does seem silly of git to not have a `--cached` equivalent for this. – Robin Winslow Oct 08 '12 at 16:02
  • 5
    Git is indeed clever enough to work this out on its own. A rename is really just an add and a delete in the repository, regardless of whether you use `git mv` or not. – Edward Thomson Oct 08 '12 at 16:10

3 Answers3

21

Git doesn't actually track renames in the repository, it uses a diff heuristic to determine if you renamed a file to another. That is to say, if you git mv a file and then replace the contents completely, it is not considered a rename, and you do not need to use git mv for it to detect renames.

For example:

% mv d.txt e.txt
% git rm d.txt
rm 'd.txt'
% git add e.txt
% git commit -m"rename without git mv"
[master f70ae76] rename without git mv
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename d.txt => e.txt (100%)
% git diff --summary --find-renames HEAD~1 HEAD
 rename d.txt => e.txt (100%)

Similarly, git mv doesn't mean that the file will be renamed, it will still use the diff algorithm:

% git mv e.txt f.txt
% echo "Completely replacing f.txt" > f.txt
% git add f.txt
% git commit -m"git mv doesn't help here"
[master 068d19c] git mv doesn't help here
 2 files changed, 1 insertion(+), 14 deletions(-)
 delete mode 100644 e.txt
 create mode 100644 f.txt
% git diff --summary --find-renames HEAD~1 HEAD
 delete mode 100644 e.txt
 create mode 100644 f.txt
Edward Thomson
  • 74,857
  • 14
  • 158
  • 187
8

By default, git will check for renames whenever a file has been removed between commits -- there's no need to tell it manually. If you do actually move a file (with git mv, rather than manually removing it and adding it again) it ignores the hint. For performance reasons, the heuristics don't run all the time -- if you move a file out of the way then add an new one with the original name, the move may not be detected.

Note that logically, git only stores the tree as it existed in each revision: it doesn't store any tracking information about changes made between revisions.

If you want to see what's detected, you can use the -M (--find-renames) switch to git diff to show renames. This switch also turns on the heuristics, which is useful if you've got a commit that doesn't trigger them.

This heuristic-based approach is a good reason (if you needed another one) to keep commits small and self-contained, as that makes git's job easier.

Andrew Aylett
  • 39,182
  • 5
  • 68
  • 95
2

git diff was not picking up that I'd renamed files and altered their contents. My scenario was I had files A, B, C, and inserted a new B, so old B was now C, and old C was now D. git diff was telling me B and C were now completely different!

The solution I used was tedious and manual, but did get the job done.

  1. Renamed the files back, as best I could. I.e. B to Bnew, C to B, D to C.
  2. Did the diff, to check no mistakes.
  3. Committed that.
  4. Used git mv to name the files back to their new names. B to C, C to D.
  5. Committed that.
  6. Did the other renames, and committed them as new files. I.e. Bnew to B.
Darren Cook
  • 27,837
  • 13
  • 117
  • 217