SleepWalker
SleepWalker

Reputation: 1261

What makes C and R filters in git diff --diff-filter=CR command?

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

Upvotes: 3

Views: 1552

Answers (2)

torek
torek

Reputation: 488825

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.

Upvotes: 5

John Szakmeister
John Szakmeister

Reputation: 47042

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.

Upvotes: 4

Related Questions