Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git diff renamed/moved and modified files but skip renamed/moved and identical files

I'm using:

git diff HEAD --diff-filter=R -M

with the meld external diff tool to show files that have been both renamed/moved and modified. I always do a git diff before doing a commit to both check my work and to properly assemble the commit message. Often times I'll rename/move files, which will also require some path changes within files that reference some renamed/moved files.

My problem is that in order to show the diff of a file that has been renamed and then modified, git diff also pops up a bunch of meld windows for files that have been renamed, but not modified. This can be very annoying. How to get git diff to skip the renamed but not modified files? I'm going to see all those files listed as being renamed/moved when I type git status in a much cleaner way, so I don't want the pop ups for identical files with git diff.

like image 512
user1748155 Avatar asked Feb 12 '16 16:02

user1748155


People also ask

How does git detect renamed files?

Git keeps track of changes to files in the working directory of a repository by their name. When you move or rename a file, Git doesn't see that a file was moved; it sees that there's a file with a new filename, and the file with the old filename was deleted (even if the contents remain the same).

How does git diff work internally?

The super-short version is that git status runs git diff . In fact, it runs it twice, or more precisely, it runs two different internal variations on git diff : one to compare HEAD to the index/staging-area, and one to compare the staging-area to the work-tree.

How do you tell the difference in files in git?

You can run the git diff HEAD command to compare the both staged and unstaged changes with your last commit. You can also run the git diff <branch_name1> <branch_name2> command to compare the changes from the first branch with changes from the second branch.

What does M mean in git diff?

^M represents carriage return. This diff means something removed a Unicode BOM from the beginning of the line and added a CR at the end.


1 Answers

I'm having the same problem and came up with an alias that did the trick (I'm using git 2.8.3)

[alias]
    difftoolr = "!git difftool \"$@\" -- .  $((git diff \"$1\" -M100% --diff-filter=R --name-only & git diff \"$1\" -M100% --diff-filter=R --name-only -R) | sed 's/\\(.*\\)/:(exclude)\\1/g') #"

How to use:

difftoolr commit1..commit2 -M

(see at the bottom for more examples)

How it works:

Imagine you have three files that have been renamed. One of them has its content actually changed (so you want to examine it using your difftool) but the other two are identical except for the path or name (and you want to ignore them).

(different) path/file.old  -> newPath/file.new
(identical) path/fileB.old -> newPath/fileB.new
(identical) path/fileC.old -> newPath/fileC.new

What the alias does is generating path specs that explicitly exclude fileB and fileC, i.e. those files that despite the renaming have the content unaltered.
In other words we are generating these filespecs:

-- . :(exclude)path/fileB.old :(exclude)newPath/fileB.new :(exclude)path/fileC.old :(exclude)newPath/fileC.new

Notice the (perhaps trickiest) part: the old file path and the new file path must be both explicitly excluded.

To do so, we issue a git diff with the following directives:

  • -M100%: follow the renames. Which means that git is is assuming that some renaming was done, and will take this info into account to do pairwise comparisons. The threshold is 100%, which instructs git to consider renamed those files which have exactly the same content, but not the ones that were altered.
  • --diff-filter=R: list only the files that git considers renamed, omit the others.
  • --names-only: just output the file names, no other info
  • -R: Reverse. In one piece of the script we swap the order of comparison (sort of switching left and right sides), so it will also output the old file names. For instance the -R directive lists the file newPath/fileB.new as path/fileB.old.
  • ...sed...: and finally we append the exclusion directive :(exclude) to the beginning of all filenames.

To deal with other cases I created three different aliases (not really elegant, I'd like something more flexible) so it can be used to specify the different kinds of comparison.

git difftoolr0 -M
git difftoolr1 HEAD~1..HEAD -M
git difftoolr2 HEAD --cached -M

In all cases the "sides of the comparisons" (commits, --cached or whatever) must be specified as the first parameter(s). For instance git difftoolr2 -M HEAD --cached won't work (because -M comes first).

[alias]
# takes no parameter: sample usage:  git difftoolr0  -M
difftoolr0 = "!git difftool \"$@\" -- .  $((git diff -M100% --diff-filter=R --name-only & git diff -M100% --diff-filter=R --name-only -R) | sed 's/\\(.*\\)/:(exclude)\\1/g') #"

# takes 1 parameter in first position: sample usage:  git difftoolr1 HEAD~1..HEAD -M
difftoolr1 = "!git difftool \"$@\" -- .  $((git diff \"$1\" -M100% --diff-filter=R --name-only & git diff \"$1\" -M100% --diff-filter=R --name-only -R) | sed 's/\\(.*\\)/:(exclude)\\1/g') #"

# takes 2 parameters in first positions: sample usage:  git difftoolr2 HEAD --cached  -M
difftoolr2 = "!git difftool \"$@\" -- .  $((git diff \"$1\" \"$2\" -M100% --diff-filter=R --name-only & git diff \"$1\" \"$2\" -M100% --diff-filter=R --name-only -R) | sed 's/\\(.*\\)/:(exclude)\\1/g') #"

Finally, BeyondCompare may suit your needs as well (and mine).
It easily allows to switch the side-by-side diff into an "unstructured" diff.

The latter strips off the folders and leaves just the names. If the filename is the same (but the paths may differ) they are considered two versions of the same file (for instance path/file.png and newPath/file.png). In this case, if the content has not changed, they can be omitted easily using a button on the upper bar.

In case "naming" conflicts occurs (several files on the same side of the comparison have the same name, under different paths), it would choose just one of them for the comparison, and ignore the others.

like image 171
Antonio Avatar answered Sep 21 '22 06:09

Antonio