关于git mv:在git中处理文件重命名

Handling file renames in git

在Git中重命名文件时,应该提交任何更改,执行重命名,然后准备重命名的文件。Git将从内容中识别该文件,而不是将其视为新的未跟踪文件,并保留更改历史记录。

但是,今晚我就这么做了,结果又回到了江户记1(0)。

1
2
3
4
5
6
7
> $ git status
# On branch master
# Changes to be committed:
#   (use"git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

将finder中的样式表从iphone.css重命名为mobile.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> $ 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来做这项工作。

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

回到我开始的地方。

1
2
3
4
5
6
7
> $ git status
# On branch master
# Changes to be committed:
#   (use"git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

我们用git mv代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> $ 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没有识别出这个名字呢?


对于git mv来说手册页说

The index is updated after successful completion,
[....]

所以,首先你必须自己更新索引(使用git add mobile.css)。然而埃多克斯1〔2〕仍将显示两个不同的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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
#

您可以通过运行获得不同的输出导致你的期待

1
2
3
4
5
6
7
8
9
10
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 statusgit commit --dry-run -a,但是这里有一个提示莱纳斯

git really doesn't even care about the whole
"rename detection" internally, and any commits you have
done with renames are totally independent of the
heuristics we then use to show the renames.

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


在Git将其识别为移动文件之前,必须将两个修改过的文件添加到索引中。

mv old newgit mv old new的唯一区别是git mv也将文件添加到索引中。

当时的mv old newgit add -A也会起作用。

请注意,不能只使用git add .,因为这不会增加索引的删除量。

请参见"git add-a"和"git add"之间的区别。


最好是自己试试。

1
2
3
4
5
6
7
8
9
10
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命令显示实际重命名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/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
#

现在去办理登机手续。

1
git commit -a -m"Rename"

现在,您可以看到文件实际上被重命名了,Git状态中显示的内容是错误的。

这个故事的寓意是:如果你不确定你的文件是否被重命名,那么就发布一个"git-commit-dry-run-a"。如果它显示文件已重命名,则可以继续。


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

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

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


你必须把新文件和git rm css/iphone.css交给git add css/mobile.css,所以git知道。然后它将在git status中显示相同的输出。

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

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

和(旧名称):

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

我认为在幕后,git mv只不过是一个包装脚本,它的作用就是:从索引中删除文件,并用不同的名称添加它。


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

Keep in mind git doesn't track any metadata about your files

您的存储库中有

1
2
3
4
5
$ cd repo
$ ls
...
iphone.css
...

受Git控制:

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

测试方法:

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

当你这样做的时候

1
$ mv iphone.css mobile.css

从Git的角度来看,

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

所以,Git建议使用它已经知道的文件(iphone.css)和它检测到的新文件(mobile.css),但只有当文件在索引中或头Git开始检查它们的内容时。

目前,"iphone.css删除"和mobile.css都不在索引中。

将iphone.css删除添加到索引

1
$ git rm iphone.css

Git会告诉你到底发生了什么:(iphone.css被删除。什么都没发生)

然后添加新文件mobile.css

1
$ git add mobile.css

这一次,删除和新文件都在索引中。现在Git检测到上下文是相同的,并将其公开为重命名。事实上,如果文件有50%的相似性,它会检测到作为一个重命名,这让您在保持操作为重命名的同时稍微更改mobile.css。

请看,这在git diff上是可复制的。既然您的文件在索引上,那么您必须使用--cached。稍微编辑mobile.css,将其添加到index并查看以下两者的区别:

1
$ git diff --cached

1
$ git diff --cached -M

-Mgit diff的"检测重命名"选项。-M代表-M50%(50%或更多的相似性将使git将其表示为重命名),但如果您经常编辑mobile.css,可以将其减少到-M20%(20%)。


步骤1:将文件从oldfile重命名为newfile

1
git mv #oldfile #newfile

步骤2:git提交并添加注释

1
git commit -m"rename oldfile to newfile"

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

1
git push origin #localbranch:#remotebranch


Git will recognise the file from the contents, rather than seeing it as a new untracked file

这就是你出错的地方。

只有在添加了文件之后,Git才能从内容中识别它。


你没有准备好你的搜寻者行动的结果。我相信如果你通过finder移动然后执行git add css/mobile.css ; git rm css/iphone.css,Git会计算新文件的散列值,然后才意识到文件的散列值是匹配的(因此这是一个重命名)。


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

解决方法很简单,但在使用Xcode提交之前必须先完成:

  • 对文件夹执行Git状态。您应该看到阶段性更改是正确的:
  • 重命名:project/oldname.h->project/newname.h重命名:project/oldname.m->project/newname.m

  • do commit-m"名称更改"
  • 然后返回到Xcode,您将看到徽章从A更改为M,现在保存它以在使用Xcode时提交进一步的更改。


    如果您真的需要手动重命名文件,例如,使用脚本批量重命名一组文件,那么使用git add -A .对我很有用。