据我了解,Git并不需要跟踪文件重命名/移动/复制操作,那么真正的目的是什么 git mv的?手册页不是特别描述性的......
它已经过时了吗?它是一个内部命令,不适合普通用户使用吗?
答案 0 :(得分:347)
git mv oldname newname
只是简写:
mv oldname newname
git add newname
git rm oldname
即。它会自动更新旧路径和新路径的索引。
答案 1 :(得分:63)
Git有一个重命名命令
git mv
,但这只是一个方便。效果 与删除文件和添加另一个文件无法区分 名称和内容相同
答案 2 :(得分:33)
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")
自动检测失败 :( 还是做到了?
$ 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
显示完整的历史记录。
现在,小心重命名,因为任一选项仍然可以产生奇怪的效果。 例如:
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 ...现在历史记录将回到 initial a 而不是 initial b ,这是错误的。因此,当我们一次做两个动作时,Git变得困惑并且没有正确地跟踪这些变化。顺便说一句,在我的实验中,当我删除/创建文件而不是使用git mv
时,同样的事情发生了。保持谨慎;你被警告了......
答案 3 :(得分:23)
正如@Charles所说,git mv
是一种速记。
这里真正的问题是“其他版本控制系统(例如Subversion和Perforce)专门处理文件重命名。为什么不Git?”
Linus在http://permalink.gmane.org/gmane.comp.version-control.git/217以特有的机智解释:
请停止此“跟踪文件”废话。 Git跟踪完全重要的事情, 即“文件集”。没有别的东西是相关的,甚至是相关的 思考它只是限制你的世界观。注意如何 CVS“注释”的概念总是不可避免地限制人们使用的方式 它。我认为这是一个完全没用的垃圾,我已经描述过了 我认为有用的东西要高出一百万倍,而且一切都失败了 完全因为我并没有把我的想法限制在错误的模型中 世界。
答案 4 :(得分:8)
我对git mv
的另一种用法没有在上面提到过。
自从发现git add -p
(git add的补丁模式;参见http://git-scm.com/docs/git-add)后,我喜欢用它来检查更改,因为我将它们添加到索引中。因此,我的工作流程变为(1)代码工作,(2)审查并添加到索引,(3)提交。
git mv
如何适应?如果直接移动文件然后使用git rm
和git add
,则所有更改都会添加到索引中,并且使用git diff查看更改不太容易(在提交之前)。但是,使用git mv
会为索引添加新路径,但不会对文件进行更改,从而允许git diff
和git add -p
照常工作。
答案 5 :(得分:2)
git mv
移动文件,更新索引以记录替换的文件路径,以及更新任何受影响的 git 子模块。与手动移动不同,它还检测仅区分大小写的重命名,否则 git 不会将其检测为更改。
在行为上类似于(虽然不完全相同)将文件从外部移动到 git,使用 git rm
从索引中删除旧路径,并使用 git add
将新路径添加到索引中。
这个问题有很多很好的部分答案。这个答案试图将它们组合成一个有凝聚力的答案。此外,任何其他答案都没有提到的一件事是,man page 实际上确实主要回答了这个问题,但它可能没有想象的那么明显。
手册页中调用了三种不同的效果:
文件、目录或符号链接在文件系统中移动:
<块引用>git-mv - 移动或重命名文件、目录或符号链接
更新索引,添加新路径并删除前一个:
<块引用>索引在成功完成后更新,但更改仍必须提交。
移动的子模块更新为在新位置工作:
<块引用>使用 gitfile 移动子模块(这意味着它们是使用 Git 版本 1.7.8 或更高版本克隆的)将更新 gitfile 和 core.worktree 设置,以使子模块在新位置工作。它还将尝试更新 gitmodules(5) 文件中的 submodule.
如this answer中提到的,git mv
非常类似于移动文件,将新路径添加到索引中,以及从索引中删除以前的路径:
mv oldname newname
git add newname
git rm oldname
然而,正如 this answer 指出的那样,git mv
在行为上与此并不完全相同。通过 git mv
移动文件会将新路径添加到索引,但不会添加文件中的任何修改内容。另一方面,使用三个单独的命令将整个文件添加到索引中,包括任何修改的内容。这在使用修补索引而不是在文件中添加所有更改的工作流时可能是相关的。
此外,如 this answer 和 this comment 中所述,git mv
具有处理 case-insensitive 但 {{3} },这在当前的 macOS 和 Windows 文件系统中很常见。例如,在此类系统中,通过 mv Mytest.txt MyTest.txt
移动文件后,git 不会检测到文件名已更改,而使用 git mv Mytest.txt MyTest.txt
会成功更新其名称。
答案 6 :(得分:1)
在特殊情况下,git mv
仍然非常有用:当您想在不区分大小写的文件系统上更改文件名的大小写时。默认情况下,APFS(mac)和NTFS(windows)都不区分大小写(但保留大小写)。
greg.kindel在评论CB Bailey的答案时提到了这一点。
假设您正在Mac上工作,并且有一个文件Mytest.txt
由git管理。您要将文件名更改为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
答案 7 :(得分:-1)
也许 git mv
自从这些答案发布后发生了变化,所以我会简要更新。在我看来,git mv
不是准确地描述为:
# not accurate: #
mv oldname newname
git add newname
git rm oldname
我经常使用 git mv 的原因有两个,之前的答案中没有描述过:
移动大型目录结构,我在其中混合了已跟踪和未跟踪文件的内容。跟踪和未跟踪的文件都会移动,并保持跟踪/取消跟踪状态
移动较大的文件和目录,我一直认为 git mv
会减少存储库 DB 历史记录的大小。这是因为移动/重命名文件是索引/引用增量。我没有验证这个假设,但它似乎是合乎逻辑的。