Git MV的目的是什么?


What's the purpose of git-mv?

据我所知,Git不需要跟踪文件重命名/移动/复制操作,所以真正的目的是什么?Git MV?该手册页没有特别描述…

它过时了吗?它是一个内部命令,而不是供普通用户使用吗?


1
git mv oldname newname

只是以下内容的简写:

1
2
3
mv oldname newname
git add newname
git rm oldname

也就是说,它会自动更新旧路径和新路径的索引。


来自官方gitfaq:

Git has a rename command git mv, but that is just a convenience. The effect
is indistinguishable from removing the file and adding another with different
name and the same content


吉特只是想帮你猜你想做什么。它正在尽一切努力保护未被打破的历史。当然,这并不完美。因此,git mv允许您明确自己的意图,并避免一些错误。

考虑这个例子。从空回购开始,

1
2
3
4
5
6
7
8
git init
echo"First">a
echo"Second">b
git add *
git commit -m"initial commit"
mv a c
mv b a
git status

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
# On branch master
# 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)
#
#   modified:   a
#   deleted:    b
#
# Untracked files:
#   (use"git add <file>..." to include in what will be committed)
#
#   c
no changes added to commit (use"git add" and/or"git commit -a")

自动检测失败:.(还是这样?

1
2
3
4
5
6
7
8
9
$ git add *
$ git commit -m"change"
$ git log c

commit 0c5425be1121c20cc45df04734398dfbac689c39
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

然后

1
2
3
4
5
6
7
8
9
10
11
12
$ git log --follow c

Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:56 2013 -0400

    change

commit 50c2a4604a27be2a1f4b95399d5e0f96c3dbf70a
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:24:45 2013 -0400

    initial commit

现在尝试一下(实验时记得删除.git文件夹):

1
2
3
4
5
6
7
git init
echo"First">a
echo"Second">b
git add *
git commit -m"initial commit"
git mv a c
git status

到现在为止,一直都还不错:

1
2
3
4
5
6
7
8
9
# On branch master
# Changes to be committed:
#   (use"git reset HEAD <file>..." to unstage)
#
#   renamed:    a -> c


git mv b a
git status

现在,没有人是完美的:

1
2
3
4
5
6
7
8
# On branch master
# Changes to be committed:
#   (use"git reset HEAD <file>..." to unstage)
#
#   modified:   a
#   deleted:    b
#   new file:   c
#

真的?但当然……

1
2
3
4
git add *
git commit -m"change"
git log c
git log --follow c

…结果和上面一样:只有--follow显示了完整的历史。

现在,小心重命名,因为任何一个选项都会产生奇怪的效果。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
git init
echo"First">a
git add a
git commit -m"initial a"
echo"Second">b
git add b
git commit -m"initial b"

git mv a c
git commit -m"first move"
git mv b a
git commit -m"second move"

git log --follow a

commit 81b80f5690deec1864ebff294f875980216a059d
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:35:58 2013 -0400

    second move

commit f284fba9dc8455295b1abdaae9cc6ee941b66e7f
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:34:54 2013 -0400

    initial b

对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
git init
echo"First">a
git add a
git commit -m"initial a"
echo"Second">b
git add b
git commit -m"initial b"

git mv a c
git mv b a
git commit -m"both moves at the same time"

git log --follow a

结果:

1
2
3
4
5
6
7
8
9
10
11
commit 84bf29b01f32ea6b746857e0d8401654c4413ecd
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:37:13 2013 -0400

    both moves at the same time

commit ec0de3c5358758ffda462913f6e6294731400455
Author: Sergey Orshanskiy <*****@gmail.com>
Date:   Sat Oct 12 00:36:52 2013 -0400

    initial a

不间断电源。。。现在历史回到了A开头,而不是B开头,这是错误的。所以,当我们一次做两个动作时,Git变得困惑,无法正确跟踪变化。顺便说一下,在我的实验中,同样的情况也发生在我删除/创建文件而不是使用git mv的时候。小心点,你已经被警告了…


正如@charles所说,git mv是一个缩写。

这里真正的问题是"其他版本控制系统(如Subversion和Perforce)专门处理文件重命名"。为什么不吉特?"

Linus在http://permalink.gmane.org/gmane.comp.version-control.git/217上解释道:

Please stop this"track files" crap. Git tracks exactly what matters,
namely"collections of files". Nothing else is relevant, and even
thinking that it is relevant only limits your world-view. Notice how the
notion of CVS"annotate" always inevitably ends up limiting how people use
it. I think it's a totally useless piece of crap, and I've described
something that I think is a million times more useful, and it all fell out
exactly because I'm not limiting my thinking to the wrong model of the
world.


我还有另外一个用途是用于上面没有提到的git mv

由于发现了git add -p(git-add的补丁模式;请参阅http://git-scm.com/docs/git-add),我喜欢在将更改添加到索引时使用它来查看更改。因此,我的工作流程变成(1)处理代码,(2)检查并添加到索引,(3)提交。

git mv如何适应?如果直接移动一个文件,然后使用git rmgit add将所有更改添加到索引中,使用git diff查看更改就不那么容易了(提交之前)。但是,使用git mv会增加索引的新路径,但不会更改文件,因此允许git diffgit add -p正常工作。


有一种特殊的情况,git mv仍然非常有用:当您想在不区分大小写的文件系统上更改文件名的大小写时。默认情况下,apfs(mac)和ntfs(windows)都不区分大小写(但保留大小写)。

Greg.Kindel在对cb贝利回答的评论中提到了这一点。

假设您在Mac上工作,并且有一个由Git管理的文件Mytest.txt。您想将文件名更改为Mytest.txt

你可以试试:

1
2
3
4
5
6
7
$ mv Mytest.txt MyTest.txt
overwrite MyTest.txt? (y/n [n]) y
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

哦,天哪。Git不承认文件有任何更改。

您可以使用以下方法来解决此问题:完全重命名文件,然后重新重命名:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mv Mytest.txt temp.txt
$ git rm Mytest.txt
rm 'Mytest.txt'
$ mv temp.txt MyTest.txt
$ git add MyTest.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use"git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt

好哇!

或者你可以通过使用git mv来省去所有的麻烦:

1
2
3
4
5
6
7
8
9
$ git mv Mytest.txt MyTest.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use"git reset HEAD <file>..." to unstage)

    renamed:    Mytest.txt -> MyTest.txt