在git中处理文件重命名


440

我读过的时候 在git中重命名文件时,应提交所有更改,执行重命名,然后暂存重命名的文件。Git将从内容中识别文件,而不是将其视为新的未跟踪文件,并保留更改历史记录。

但是,今晚仅此一次,我最终恢复为git mv

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

将我在Finder中的样式表从重命名iphone.cssmobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

所以git现在认为我已经删除了一个CSS文件,并添加了一个新文件。不是我想要的,让撤消重命名,让git完成工作。

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

回到我开始的地方。

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

让我们git mv改为使用。

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

看起来我们很好。那么,为什么在我使用Finder时git第一次没有识别出重命名?


29
Git跟踪内容,而不是文件,因此无论如何使索引进入正确的状态都无关紧要- add+rmmv-它会产生相同的结果。然后,Git使用其重命名/复制检测来告知您它是重命名。您引用的来源也不准确。是否在同一提交中修改+重命名实际上并不重要。当您在修改和重命名之间进行比较时,重命名检测会将其视为重命名+修改,或者如果修改是完全重写,它将显示为已添加和已删除-仍然无关紧要它。
卡斯卡贝尔

6
如果是这样,为什么没有使用Finder用我的重命名检测到它?
格雷格(Greg K)2010年

26
git mv old new自动更新索引。当您在Git外部重命名时,您将必须执行git add newgit rm old并将更改暂存到索引。完成此操作后,git status将按预期工作。
克里斯·约翰森

4
我只是将一堆文件移动到了public_htmlgit目录中。执行git add .和之后git commit,它仍在中显示了一堆“已删除”文件git status。我执行了git commit -a,删除操作已提交,但是现在我没有关于现在存在文件的历史记录public_html。这个工作流程没有我想要的顺利。
格雷格·K

Answers:


352

对于git mv手册

成功完成后,索引将更新,[…]

因此,首先,您必须自己更新索引(使用git add mobile.css)。但是
git status 仍然会显示两个不同的文件

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

通过运行git commit --dry-run -a,您可以获得不同的输出 ,从而得到您期望的结果:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

我无法确切告诉您为什么我们看到git status和之间的这些差异
git commit --dry-run -a,但这是Linus的提示 :

git甚至根本不在乎内部的整个“重命名检测”,并且您对重命名所做的任何提交都完全独立于我们随后用来显示重命名的试探法。

A dry-run使用真正的重命名机制,而a git status可能没有。


1
您没有提及您所做的步骤git add mobile.css。没有它git status -a,只会“看到”以前跟踪的iphone.css文件的删除,而不会触及新的未跟踪的mobile.css文件。另外,git status -a在Git 1.7.0及更高版本中无效。““ git status”不再是“ git commit --dry-run”。” 在kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt中git commit --dry-run -a如果需要此功能,请使用。正如其他人所说,只需更新索引,git status即可按OP的预期工作。
克里斯·约翰森

3
如果执行正常操作git commit,它将不会提交重命名的文件,并且工作树仍然相同。git commit -a几乎击败了git工作流程/思维模型的每个方面-每次更改都已提交。如果您只想重命名文件,但是index.html在另一个提交中提交更改怎么办?
knittl

@Chris:是的,我当然要补充一点mobile.css,我应该提到。但这就是我的回答的重点:手册页指出了the index is updated您使用时的情况git-mv。感谢您的status -a澄清,我使用git 1.6.4
tanascius

4
好答案!我用力撞墙,试图弄清为什么git status没有检测到重命名。git commit -a --dry-run添加我的“新”文件后运行,显示了重命名,并最终给了我信心!
stephen.hanson 2013年

1
在git 1.9.1中,git status现在的行为类似于git commit
JacquesRenéMesrine

77

您必须将两个修改后的文件添加到索引,然后git才能将其识别为移动。

mv old new和之间的唯一区别git mv old new是git mv还将文件添加到索引中。

mv old new 然后 git add -A本来也可以

请注意,您不能只使用 git add .因为那不会为索引添加删除内容。

请参见“ git add -A”和“ git add”之间的区别。


3
感谢您提供的git add -A链接,它非常有用,因为我一直在寻找这样的捷径!
PhiLho 2012年

5
请注意,使用git 2时,git add . 确实会将删除添加到索引中。
Nick McCurdy 2014年

19

最好的办法是自己尝试一下。

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

现在git status和git commit --dry-run -a显示两个不同的结果,其中git status显示bbb.txt作为新文件/ aaa.txt被删除,而--dry-run命令显示实际的重命名。

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

现在,继续办理登机手续。

git commit -a -m "Rename"

现在您可以看到该文件实际上已重命名,并且git status中显示的内容是错误的。

故事的寓意:如果不确定文件是否已重命名,请发出“ git commit --dry-run -a”。如果它显示文件已重命名,那就很好了。


3
对于Git重要的事情,两者都是正确的。不过,正如提交者可能看到的那样,后者更接近您。重命名和删除+创建之间的真正区别仅在于操作系统/文件系统级别(例如,相同的inode#与新的inode#),Git对此并不十分在意。
Alois Mahdal

15

对于git 1.7.x,以下命令对我有用:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

不需要git add,因为原始文件(即css / mobile.css)以前已经在提交的文件中。


5
这个。所有其他答案都是荒谬且不必要的复杂。这将维护两次提交之间的文件历史记录,以便文件重命名之前/之后的合并不会被破坏。
丰满的

10

您必须到git add css/mobile.css新文件并git rm css/iphone.css,所以git知道了。然后它将在显示相同的输出git status

您可以在状态输出(文件的新名称)中清楚地看到它:

# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

和(旧名称):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

我认为幕后git mv只不过是一个包装脚本,它确实做到了这一点:从索引中删除文件并以不同的名称添加它


我认为我不必这样做,git rm css/iphone.css因为我认为这将删除现有的历史记录。也许我误会了git中的工作流程。
格雷格K

4
@Greg K:git rm不会删除历史记录。它仅从索引中删除一个条目,以便下一次提交将没有该条目。但是,它仍然存在于祖先提交中。您可能会感到困惑的是(例如)git log -- new将在您提交的位置停止git mv old new。如果要遵循重命名,请使用git log --follow -- new
克里斯·约翰森

9

让我们从git的角度考虑您的文件。

请记住,git不会跟踪有关文件的任何元数据

您的存储库有(以及其他)

$ cd repo
$ ls
...
iphone.css
...

它在git控制下:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

使用以下方法进行测试:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

当你做

$ mv iphone.css mobile.css

从git的角度来看,

  • 没有iphone.css(它被删除-git警告-)。
  • 有一个新文件mobile.css
  • 这些文件完全无关。

因此,git会提供有关它已经知道的文件(iphone.css)和它检测到的新文件(mobile.css)的建议,但是仅当文件在索引中或HEAD时git才开始检查其内容。

目前,“ iphone.css删除”和mobile.css都不在索引上。

将iphone.css删除添加到索引

$ git rm iphone.css

git会告诉您确切的情况:(iphone.css已删除。什么也没有发生)

然后添加新文件mobile.css

$ git add mobile.css

这次删除和新文件都在索引上。现在,git检测到上下文是相同的,并将其公开为重命名。实际上,如果文件相似度为50%,它将检测为重命名,从而使您可以更改mobile.css同时将操作保留为重命名。

看到这是可复制的git diff。现在您的文件已在索引上,您必须使用--cached稍微编辑一下mobile.css,将其添加到索引中,看看它们之间的区别:

$ git diff --cached 

$ git diff --cached -M

-M是的“检测重命名”选项git diff-M代表-M50%(50%或更高的相似度会使git将其表示为重命名),但是-M20%如果您大量编辑mobile.css,则可以将其减少为(20%)。


8

步骤1:将文件从旧文件重命名为新文件

git mv #oldfile #newfile

步骤2:git commit并添加评论

git commit -m "rename oldfile to newfile"

第三步:将此更改推送到远程服务器

git push origin #localbranch:#remotebranch

1
请添加一些评论,以便对OP有所帮助
Devrath,2014年

步骤2是不必要的。之后git mv,新文件已在索引中。
2014年

7

Git将从内容中识别出文件,而不是将其视为新的未跟踪文件

那就是你错了。

只有添加文件之后,git才能从内容中识别出它。


究竟。登台时,git将正确显示重命名。
Max MacLeod

3

您没有上演取景器移动的结果。我相信,如果您先通过Finder进行了移动,然后git add css/mobile.css ; git rm css/iphone.cssgit进行了计算,则新文件的哈希值才意识到文件的哈希值匹配(因此是重命名)。


2

如果确实需要手动重命名文件,例如。使用脚本批量重命名一堆文件,然后使用git add -A .对我有用。


2

对于Xcode用户:如果在Xcode中重命名文件,则会看到徽章图标更改为追加。如果使用XCode进行提交,则实际上将创建一个新文件并丢失历史记录。

解决方法很简单,但是您必须在使用Xcode进行提交之前执行此操作:

  1. 在您的文件夹上执行git Status。您应该看到分阶段的更改是正确的:

重命名:Project / OldName.h-> Project / NewName.h重命名:Project / OldName.m-> Project / NewName.m

  1. 提交-m'名称更改'

然后回到XCode,您将看到徽章从A更改为M,并保存以立即使用xcode进行进一步更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.