我有一个我已重命名然后编辑过的文件。我想告诉Git进行重命名,但不是内容修改。也就是说,我希望删除旧文件名,并添加旧文件内容和新文件名。
所以我有这个:
Changes not staged for commit:
deleted: old-name.txt
Untracked files:
new-name.txt
但要么:
Changes to be committed:
new file: new-name.txt
deleted: old-name.txt
Changes not staged for commit:
modified: new-name.txt
或者这个:
Changes to be committed:
renamed: old-name.txt -> new-name.txt
Changes not staged for commit:
modified: new-name.txt
(相似性度量应为100%)。
我想不出一种直截了当的方法。
是否有获取特定文件特定修订内容的语法,并将其添加到指定路径下的git临时区域?
删除部分git rm
很好:
$ git rm old-name.txt
这是我正在努力的重命名的补充部分。 (我可以保存新内容,签出一个新副本(旧内容),mv
在shell中,git add
,然后恢复新内容,但这似乎是一个很长的路要走!)
谢谢!
答案 0 :(得分:24)
Git并没有真正重命名。他们&#39>所有计算在"事后" fashion:git将一个提交与另一个提交进行比较,在比较时,决定是否存在重命名。这意味着git是否会考虑某些事情"重命名"动态变化。我知道你已经问过一个你甚至没有做过的承诺,但是请耐心等待,这真的一切都确实存在(但答案很长)。
当您询问git(通过git show
或git log -p
或git diff HEAD^ HEAD
)"最后一次提交时发生的事情",它会运行前一次提交的差异( HEAD^
或HEAD~1
或前一次提交的实际原始SHA-1(其中任何一项将识别它)和当前提交(HEAD
)。在制作这种差异时,它可能会发现曾经有old.txt
并且不再存在;并且没有new.txt
但现在有了。
这些文件名 - 曾经存在但不存在的文件,以及那些现在不存在的文件 - 被放入标记为"候选者进行重命名"。然后,对于堆中的每个名称,git比较旧内容"和"新内容"。由于git将内容减少到SHA-1的方式,完全匹配的比较非常简单;如果完全匹配失败,git切换到可选的"内容至少相似" diff来检查重命名。使用git diff
此可选步骤由-M
标志控制。使用其他命令,它可以通过git config
值设置,也可以硬编码到命令中。
现在,回到临时区域和git status
:索引/登台区域中的git存储基本上是#34;下一次提交的原型"。当你git add
时,git会在那时存储文件内容,计算进程中的SHA-1,然后将SHA-1存储在索引中。当你git rm
某事时,git会在索引中存储一条说明"这个路径名在下次提交时被故意删除"。
然后,git status
命令只是做一个diff或者两个差异:HEAD
vs index,用于提交的内容;和index vs work-tree,用于可以(但尚未提交)的内容。
在第一个差异中,git使用与以往相同的机制来检测重命名。如果HEAD
提交中的路径在索引中消失了,并且索引中的路径是新的而不在HEAD
提交中,那么它就是sa' sa重命名检测的候选人。 git status
命令将检测重命名为" on" (并且文件计数限制为200;只有一个重命名检测候选者这个限制很多)。
这对您的案件意味着什么?好吧,你重命名了一个文件(不使用git mv
,但它并不重要,因为git status
在git status
时找到了重命名,或者找不到它,并且现在有一个更新,不同版本的新文件。
如果你git add
新版本,那个较新的版本会进入repo,而它的SHA-1在索引中,当git status
执行diff时,它会比较新旧版本。如果他们至少" 50%相似" (git status
的硬连线值),git会告诉你文件已重命名。
当然,git add
- 已修改的内容并不是您要求的:您想要进行中间提交,其中文件仅 重命名,即使用新名称的树提交,但旧内容。
由于上述所有动态重命名检测,您不会 执行此操作。如果你想这样做(无论出于何种原因)......好吧,git并没有那么容易。
最简单的方法就像你建议的那样:将修改后的内容移到某个地方,使用git checkout -- old-name.txt
,然后git mv old-name.txt new-name.txt
,然后提交。 git mv
将重命名索引/登台区域中的文件,并重命名工作树版本。
如果git mv
有--cached
之类的git rm
选项,那么您可以git mv --cached old-name.txt new-name.txt
然后git commit
。第一步是重命名索引中的文件,而不触及工作树。但它并没有:它坚持要覆盖工作树版本,它坚持要在工作树中存在旧名称才能开始。
在不触及工作树的情况下执行此操作的单步方法是使用git update-index --index-info
,但这也有点混乱(无论如何我会立即显示它)。幸运的是,我们可以做的最后一件事。通过将旧名称重命名为新名称并修改文件,我已经设置了相同的情况:
$ 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)
deleted: old-name.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
new-name.txt
我们现在所做的是,首先,手动将文件放回旧名称下,然后使用git mv
再次切换到新名称:
$ mv new-name.txt old-name.txt
$ git mv old-name.txt new-name.txt
此时git mv
更新索引中的名称,但将原始内容保留为索引SHA-1,但移动工作树版本 (新内容)在工作树中到位:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: old-name.txt -> new-name.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: new-name.txt
现在只需要git commit
进行重命名提交,而不是新内容。
(请注意,这取决于没有旧文件的新文件!)
使用git update-index
怎么样?好吧,首先让我们回到工作树中的变化,索引匹配HEAD commit&#34;状态:
$ git reset --mixed HEAD # set index=HEAD, leave work-tree alone
现在让我们看看old-name.txt
索引中的内容:
$ git ls-files --stage -- old-name.txt
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt
因此,我们需要git update-index --index-info
做的是清除old-name.txt
的条目,但为new-name.txt
创建一个相同的条目:
$ (git ls-files --stage -- old-name.txt;
git ls-files --stage -- old-name.txt) |
sed -e \
'1s/^[0-9]* [0-9a-f]*/000000 0000000000000000000000000000000000000000/' \
-e '2s/old-name.txt$/new-name.txt/' |
git update-index --index-info
(注意:我为了发布目的打破了上面的内容,当我输入时它就是一行;在sh / bash中,它应该像这样分解,给定我添加的反斜杠以继续&# 34; sed&#34;命令)。
还有其他一些方法可以做到这一点,但只需提取索引条目两次并将第一个修改为删除,第二个使用新名称似乎最简单,因此sed
命令。第一次替换改变文件模式(100644,但任何模式将变为全零)和SHA-1(匹配任何SHA-1,替换为git&#39;特殊全零SHA-1),第二次替换在替换名称时单独保留模式和SHA-1。
当update-index完成时,索引记录了旧路径的删除和新路径的添加(使用与旧路径中相同的模式和SHA-1)。
请注意,如果索引具有old-name.txt
的未合并条目,则可能会失败,因为该文件可能还有其他阶段(1到3)。
答案 1 :(得分:9)
@torek给出了一个非常明确和完整的答案。那里有很多非常有用的细节;值得正确阅读。
但是,为了那些匆忙的人,给出了一个非常简单的解决方案的关键是:
我们现在所做的是,首先,手动将文件放回旧版本 name,然后使用git mv再次切换到新名称:
$ mv new-name.txt old-name.txt $ git mv old-name.txt new-name.txt
(只是我失踪的mv
后面,才能使git mv
成为可能。)
如果您觉得有帮助,请选择 @ torek的答案。