4

I've got a few files that I moved from a root folder into a lib/ folder. I've also made some significant changes to those files as well.

The git history shows the old file as deleted and the new one as added. But even though there are a lot of differences in the files, I still want them to be considered the same file. Is it possible to go back and tell git that this file is a move, as opposed to a delete and add?

ewok
  • 20,148
  • 51
  • 149
  • 254
  • That's not up to you a "move" is internally a delete and an add anyway. – Mad Physicist Aug 06 '18 at 17:47
  • @MadPhysicist except that the file history gets lost in a remove and add, whereas with a move file history is maintained – ewok Aug 06 '18 at 17:48
  • Add a note in your commit message then. The history of the old file won't be lost. – Mad Physicist Aug 06 '18 at 17:53
  • 1
    Possible duplicate of [How to make git mark a deleted and a new file as a file move?](https://stackoverflow.com/questions/433111/how-to-make-git-mark-a-deleted-and-a-new-file-as-a-file-move) – Mad Physicist Aug 06 '18 at 17:54
  • It looks like you'll have to split the commit up, which is possible with an interactive rebase or similar. – Mad Physicist Aug 06 '18 at 17:54

1 Answers1

8

Git does not track copies and renames. Instead, it looks at the file contents and decides whether it's a rename or a copy.

This has advantages and disadvantages, but acknowledges that people working with version control often won't do basic shell commands, like mv or rm or cp, through the version control system. It's also because Git stores file contents and their names separately (blob vs tree objects). So Git takes a guess. This is also why there is no git-cp.

Fortunately the threshold for what Git considers a copy or rename is configurable. git-log and git-diff both accept -M.

  -M[<n>], --find-renames[=<n>]
      If generating diffs, detect and report renames for each commit. For following
       files across renames while traversing history, see --follow. If n is specified,
       it is a threshold on the similarity index (i.e. amount of addition/deletions
       compared to the file's size). For example, -M90% means Git should consider a
       delete/add pair to be a rename if more than 90% of the file hasn't changed.
       Without a % sign, the number is to be read as a fraction, with a decimal point
       before it. I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly,
       -M05 is the same as -M5%. To limit detection to exact renames, use -M100%. The
       default similarity index is 50%.

Unfortunately, this affects all files and there's no way to save this as part of the repository.


But even though there are a lot of differences in the files, I still want them to be considered the same file. Is it possible to go back and tell git that this file is a move, as opposed to a delete and add?

Yes. Instead of one big commit that both renames and makes a big change, split it up into two commits. One to rename the file, and one to change the content. Then Git will clearly see the continuity. Use git rebase -i to split up the commit.

In some cases this is simple. In others the rename alone will break the code requiring a change in the same commit to keep things working. Often you can do a minimum change, like just renaming a class to match the new filename, which Git will recognize as the same file, leaving larger changes for the next commit.

If your file is so small that it's primarily things like class names which are attached to the filename, that may indicate a code smell and a candidate for refactoring.

Schwern
  • 153,029
  • 25
  • 195
  • 336