3

I can't understand what means "Copied" and "Renamed" for git diff-filter, according to this answer: https://stackoverflow.com/a/6879568/2039203. When I rename/move some file, than this file is handled as "Added" and "Deleted".

So the question is: what should I do to get some files filtered with the following two commands? Do I need this filter options to get the list of the added/deleted files?

git diff --diff-filter=C --name-only

and

git diff --diff-filter=R --name-only
Community
  • 1
  • 1
SleepWalker
  • 1,152
  • 14
  • 17
  • I'm not sure I understand your last question: what do you mean by "Do I need this filter options to get the list of the added/deleted files?" Are you looking for a list of files that were changed? Is `git status` not telling you what you want? – John Szakmeister Feb 11 '14 at 09:53
  • No, I need this list for another purpose. I want to have something like this: `git diff --name-only --diff-filter=A 8b8aeb0 > newFiles` and than to use this output for my needs :) – SleepWalker Feb 11 '14 at 11:23

2 Answers2

5

git diff will show a file as "renamed" if it passes a "similarity test". To make sure it's occurring, specify the -M option (I'll do that below).1

$ mkdir /tmp/temprepo && cd /tmp/temprepo && git init
$ cat << end > file
> Here is some text for a file.
> We want it to be long enough
> that renames and copies can
> be detected fairly easily.
> If we put ten lines into the
> file, then each percent of
> similarity is one tenth of
> each of the lines, i.e., each
> line represents 10% similarity
> to the earlier file.
> end
$ git commit -m initial
[master (root-commit) 842824a] initial
 1 file changed, 10 insertions(+)
 create mode 100644 file
$ git mv file newname
$ git diff --cached --name-status
R100    file    newname

Let's go ahead and commit this so that we can git diff two commits, rather than the commit and the index:

$ git commit -m B
[master a2380eb] B
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file => newname (100%)

Just to see what that did:

$ git diff --name-status -M HEAD^ HEAD
R100    file    newname

Now let's make a minor change:

$ ed newname
279
9s/10/about 10/p
line represents about 10% similarity
w
285
q
$ git commit -a -m C
[master 57fce41] C
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git diff --name-status -M HEAD~2 HEAD
R087    file    newname

The diff (with --name-status mode selected) between HEAD~2 (the initial commit) and the latest commit now shows that file file was renamed to newname with only 87% similarity, rather than 100% similarity (as now seen between HEAD~2 and HEAD~1).2

To see copies you generally have to run git diff with -C and/or --find-copies-harder.

You can control how similar files must be to have renames (and copies) detected with numeric values after -M (and -C), e.g., -M90% causes the above git diff --name-status to treat the rename as an add-and-delete pair:

$ git diff --name-status -M90% HEAD~2 HEAD
D       file
A       newname

The -B option also affects rename detection, as do git config settings diff.renameLimit and diff.renames (see the documentation). To defeat rename detection entirely—which makes writing git hook scripts with --diff-filter a lot easier in many cases—use --no-renames (or any of the porcelain commands, which don't obey diff.renames; see also footnote 1).


1This similarity test defaults to "50% similar" when diff.renames is enabled and "exact match only" when it is disabled. In Git versions prior to 2.9, diff.renames defaulted to false (disabled); in 2.9 and later it defaults to true (enabled) and can also be set to copy or copies to enable the -C option as well. Note that the diff.renames setting applies to git diff, which is what Git calls a porcelain (or user-facing) command, but not to, e.g., git diff-tree, which is what Git calls a plumbing command. You must use the -M or --find-renames option if you want the plumbing commands to do rename detection.

2Earlier, we used HEAD^ and HEAD, but we added commit C since then, so the initial commit is now HEAD^^ or HEAD~2.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you. Especially for `--no-renames`. – SleepWalker Feb 12 '14 at 08:17
  • Since this answer, I belive the defaults have changed to `diff.renames=true` since `2.9` . Also, I believe `diff.renames=false` does not imply "exact match only", which one of the footnotes implies, see `62df1e68e0086d9d8dc65a76d99f72c265dcf852` – CervEd Dec 23 '22 at 19:40
  • 1
    @CervEd: correct, the default changed in 2.9, I'll update. As for the commit you mention, that's a documentation update meant to clarify that `diff.renames=false` turns it off (which it still does) and that plumbing commands don't use `diff.renames`. – torek Dec 24 '22 at 00:24
3

You need a little more than that since copy and rename detection isn't enable by default.

Try this:

git init example
cd example
echo foo > foo.txt
echo baz > baz.txt
git add .
git commit -m "add foo and baz"
cp baz.txt other-baz.txt
git mv foo.txt bar.txt
git add other-baz.txt

At this point, you should see something like this in git status -s:

## master
R  foo.txt -> bar.txt
A  other-baz.txt

Two things to make note of here: git status does rename detection, but it's not doing copy detection. So it tells you about the rename, but it's showing the copy as an add. Also, the changes are staged. So a plain git diff isn't going to show anything.

To see --diff-filter=C and --diff-filter=R work, you need to tell git diff to do copy and rename detection, and you need to allow it to look at the staging area (called the "index" in the git documentation). Try this:

$ git diff --staged -C -C --diff-filter=C --name-only
other-baz.txt

$ git diff --staged -C --diff-filter=R --name-only
bar.txt

In this case, the copy requires specifying -C twice to make Git look at previous commits when doing copy detection (since it's the copy of a previously committed file). You can also use --find-copies-harder to enable this behavior.

John Szakmeister
  • 44,691
  • 9
  • 89
  • 79