4

The documentation for git status [1] implies that it should be able to detect renames and copies (with the C state) or no matter what git diff -C should do it, but neither appear to work:

mkdir test
cd test/
git init
echo 'Hello World!' > hello.txt
echo 'Goodbye World!' > goodbye.txt
git add -A
git commit -m "Initial commit"

cp hello.txt copied.txt
mv goodbye.txt moved.txt
git add -A

$ git status --short
A  copied.txt  <------------ NO COPY DETECTED
R  goodbye.txt -> moved.txt

$ git diff -M -C --summary --cached
 create mode 100644 copied.txt  <------------ NO COPY DETECTED
 rename goodbye.txt => moved.txt (100%)

$ git commit -m Test
$ git diff -M -C --summary HEAD~
  create mode 100644 copied.txt  <------------ NO COPY DETECTED
  rename goodbye.txt => moved.txt (100%)

Side related question: is it possible to have git status or git diff detect copies and renames in the workdir prior to adding the changes to the index?

[1] http://git-scm.com/docs/git-status

Pol
  • 3,848
  • 1
  • 38
  • 55

2 Answers2

6

Since nobody on SO apparently knew the answer to the main question about git status, I ended up asking the question on the official Git mailing list as well [1]:

About git status not detecting the file copy (the doc is actually wrong, it just doesn't detect copies at all):

git-status has used renames since mid-2005. The documentation mentioning copies was added much later, along with the short and porcelain formats. That code handles whatever the diff engine throws at it. I don't think anybody considered at that time the fact that you cannot actually provoke status to look for copies.

About git diff -C not detecting the file copy (you need to also pass --find-copies-harder):

By default, -C only finds copies when the source file was also modified in the same commit. Since you did not modify hello.txt in the same commit where you copied it to copied.txt, it will not be considered.

If you pass -C -C (twice), or use --find-copies-harder, Git will consider all files in the repository. Note that this can be slower, which is the reason why it's not the default.

[1] http://marc.info/?l=git&m=141730775928542&w=2

Pol
  • 3,848
  • 1
  • 38
  • 55
2

is it possible to have git status or git diff detect copies and renames in the workdir prior to adding the changes to the index?

No, as mentioned in "Git changing repo directory and files directory".
Git will detect rename on full (committed) tree, not on partial one added to the index.

Once you commit, the diff should detect the move/rename:

git diff -M -C --summary @~

(with @ meaning HEAD)


Regarding the copy, considering file copied wasn't modified in that commit, you must use the "--find-copies-harder" option:

C:\Users\vonc\prog\git\test\mv\test>git diff -M -C --summary --cached
 create mode 100644 copied.txt
 rename goodbye.txt => moved.txt (100%)

vs.

C:\Users\vonc\prog\git\test\mv\test>git diff -M -C --find-copies-harder --summary --cached
 copy hello.txt => copied.txt (100%)
 rename goodbye.txt => moved.txt (100%)

That is:

--find-copies-harder

For performance reasons, by default, -C option finds copies only if the original file of the copy was modified in the same changeset.
This flag makes the command inspect unmodified files as candidates for the source of copy.
This is a very expensive operation for large projects, so use it with caution.
Giving more than one -C option has the same effect.


Note that with Git 2.17 (Q2 2018), the git status output is now more precise.

See commit e4e5da2 (15 Feb 2018) by Stefan Beller (stefanbeller).
(Merged by Junio C Hamano -- gitster -- in commit 7676b86, 28 Feb 2018)

It is possible to have the output ' A' from 'git status --porcelain' by adding a file using the '--intend-to-add' flag.
Make this clear by adding the pattern in the table of the documentation.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Then this is inconsistent with the Git docs which says in http://git-scm.com/docs/git-status that `git status` can report copies and renames. – Pol Nov 30 '14 at 00:10
  • @Pol once committed, yes, git status can report such renames. Not before a commit. – VonC Nov 30 '14 at 00:12
  • I edited the test script in my question to add a commit, but it's still not detecting the copy afterwards. – Pol Nov 30 '14 at 00:13
  • What do you mean git status can report renames after committed? Git status only report the state of the worktree, not the "state" of commits. – Pol Nov 30 '14 at 00:14
  • @Pol it follows an heuristic: http://stackoverflow.com/a/21292993/6309 In your case, more than 50% changed, hence the non-detection – VonC Nov 30 '14 at 00:14
  • What do you mean more than 50% changed? It's the exact same file. – Pol Nov 30 '14 at 00:14
  • @Pol you have quite a few examples of mv and `git status` cs. `git commit --dry-run -a` behavior in http://stackoverflow.com/q/2641146/6309. – VonC Nov 30 '14 at 00:18
  • @Pol for the mv part, once added to the index at least, `git diff -M --summary master` might work better. Example: http://www.reddit.com/r/git/comments/1oi0tw/is_there_any_way_have_git_diff_show_that_a_file/ – VonC Nov 30 '14 at 00:21
  • @Pol considering move and rename have thresholds, did you try `git diff -M -C100 --summary --cached` – VonC Nov 30 '14 at 00:29
  • @Pol I just tested your use case, and edited the answer: for detecting rename, you have to use the `--find-copies-harder` option. – VonC Nov 30 '14 at 00:34
  • So `--find-copies-harder` solves the `git diff` case but you say this is because "considering the small size of the files involve[d]". However the doc you quote mentions nothing about file size? – Pol Nov 30 '14 at 00:43
  • So that solves the `git diff` case which is great, but there's still the `git status` case: the docs says it should be able to detect both renames and copies. – Pol Nov 30 '14 at 00:47
  • @Pol renames, yes. Copies? I didn't see it. Not sure how or when a status sees a copy. – VonC Nov 30 '14 at 01:01
  • @Pol interesting read: http://git.661346.n2.nabble.com/How-to-fork-a-file-git-cp-tp6331860p6332357.html – VonC Nov 30 '14 at 01:04