git-mv的目的是什么?

根据我的理解,Git并不真的需要跟踪文件重命名/移动/复制操作,那么真正的目的是什么呢 git mv吗?手册页没有特别的描述性

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

166710 次浏览
git mv oldname newname

是:

mv oldname newname
git add newname
git rm oldname

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

官方GitFaq:

Git有一个重命名命令git mv,但这只是为了方便。的影响 是难以区分的删除文件和添加另一个不同的 名称和相同的内容

正如@Charles所说,git mv是一个简写。

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

Linus在http://permalink.gmane.org/gmane.comp.version-control.git/217用他特有的机智解释道:

请停止这种“跟踪文件”的废话。Git跟踪完全, 即“文件集合”。其他的都无关紧要,甚至 思考,它是相关的,只限制你的世界观。注意 CVS“注释”的概念总是不可避免地限制人们的使用方式 它。我认为这完全是一堆没用的垃圾,我已经描述过了 一些我认为比它有用一百万倍的东西,然后就全掉下来了 完全,因为我没有限制我的思维错误的模型 世界。< / p >

Git只是试图为您猜测您要做什么。它正在尽一切努力保存完整的历史。当然,它并不完美。所以git mv允许你明确你的意图并避免一些错误。

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

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
mv a c
mv b a
git status

结果:

# 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")
< p > Autodetection失败:( 果真如此吗?< / p >
$ 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

然后

$ 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文件夹):

git init
echo "First" >a
echo "Second" >b
git add *
git commit -m "initial commit"
git mv a c
git status

到目前为止还不错:

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




git mv b a
git status

没有人是完美的:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a
#   deleted:    b
#   new file:   c
#

真的吗?但是当然……

git add *
git commit -m "change"
git log c
git log --follow c

...结果与上面相同:只有--follow显示完整的历史。


现在,小心重命名,如任何一种选择都可能产生奇怪的效果。 例子:< / p >

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

对比一下:

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

结果:

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

Ups……现在历史将返回到最初的一个而不是最初的b,这是错误的。因此,当我们一次移动两步时,Git变得混乱,无法正确跟踪更改。顺便说一下,在我的实验中,当我删除/创建文件而不是使用git mv时,也发生了同样的情况。小心地进行;我警告过你……

上面没有提到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)都是不区分大小写的(但保留大小写)。

格雷格。金德尔在对CB Bailey的回答的评论中提到了这一点。

假设你在mac上工作,git管理一个文件Mytest.txt。您要将文件名更改为MyTest.txt

你可以试试:

$ 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不承认文件有任何更改。

可以可以通过完全重命名文件,然后重命名回来来解决这个问题:

$ 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来省去这些麻烦:

$ 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

git mv移动文件,更新索引以记录被替换的文件路径,以及更新任何受影响的git子模块。与手动移动不同的是,它还会检测到git不会作为更改检测到的纯大小写重命名。

它的行为与将文件从外部移动到git类似(尽管不完全相同),使用git rm从索引中删除旧路径,并使用git add将新路径添加到索引中。

回答的动机

这个问题有很多不错的部分答案。这个答案试图将它们组合成一个单一的有凝聚力的答案。此外,任何其他答案都没有指出的一件事是,手册页实际上主要回答了这个问题,但它可能不那么明显。

详细解释

手册页中显示了三种不同的效果:

  1. 文件、目录或符号链接在文件系统中移动:

    git-mv -移动或重命名文件、目录或符号链接

  2. 索引被更新,添加新的路径并删除之前的路径:

    索引在成功完成后更新,但仍然必须提交更改。

  3. 移动的子模块被更新为在新的位置工作:

    使用gitfile(这意味着它们是用Git 1.7.8或更新版本克隆的)移动子模块将更新gitfile和core。工作树设置,使子模块在新位置工作。它还将尝试更新子模块.<name>在gitmodules (5)文件中设置路径并执行该文件(除非使用-n)。

正如在这个答案中提到的,git mv非常类似于移动文件,将新路径添加到索引中,并从索引中删除之前的路径:

mv oldname newname
git add newname
git rm oldname

然而,正如这个答案所指出的,git mv在行为上并不严格相同。通过git mv移动文件将新路径添加到索引中,但不添加文件中任何修改的内容。另一方面,使用这三个单独的命令将整个文件添加到索引中,包括任何修改过的内容。当使用修补索引的工作流,而不是在文件中添加所有更改时,这可能是相关的。

此外,正如这个答案这样的评论中所提到的,git mv还有一个额外的好处,即在不区分大小写case-preserving的文件系统上处理只区分大小写的重命名,这在当前的macOS和Windows文件系统中经常出现。例如,在这样的系统中,git在通过mv Mytest.txt MyTest.txt移动文件后不会检测到文件名已更改,而使用git mv Mytest.txt MyTest.txt将成功更新其名称。

也许git mv在这些答案发布后发生了变化,所以我将简要更新。在我看来,git mv<强> < / >强不,准确地描述为:

 # not accurate: #
mv oldname newname
git add newname
git rm oldname

我经常使用git mv有两个原因,在之前的回答中没有描述:

  1. 移动大目录结构,在那里我有混合的内容跟踪和未跟踪的文件。被跟踪和未跟踪的文件都将移动,并保持其跟踪/未跟踪状态

  2. 移动较大的文件和目录,我一直假设git mv将减少存储库DB历史大小。这是因为移动/重命名文件是索引/引用增量。我还没有证实这个假设,但它似乎是合乎逻辑的。