Rhu*_*arb 19 git file-rename git-add
我有一个文件,我已重命名,然后编辑.我想告诉Git进行重命名,但不是内容修改.也就是说,我希望删除旧文件名,并添加旧文件内容和新文件名.
所以我有这个:
Changes not staged for commit:
deleted: old-name.txt
Untracked files:
new-name.txt
Run Code Online (Sandbox Code Playgroud)
但想要这个:
Changes to be committed:
new file: new-name.txt
deleted: old-name.txt
Changes not staged for commit:
modified: new-name.txt
Run Code Online (Sandbox Code Playgroud)
或这个:
Changes to be committed:
renamed: old-name.txt -> new-name.txt
Changes not staged for commit:
modified: new-name.txt
Run Code Online (Sandbox Code Playgroud)
(相似性度量应为100%).
我想不出一个直截了当的方法来做到这一点.
是否有获取特定文件的特定修订内容的语法,并将其添加到指定路径下的git临时区域?
删除部分,git rm很好:
$ git rm old-name.txt
Run Code Online (Sandbox Code Playgroud)
这是我正在努力的重命名的补充部分.(我可以保存新内容,mv在shell中签出一份新的副本(旧内容),git add然后恢复新内容,但这似乎有很长的路要走!)
谢谢!
tor*_*rek 25
Git并没有真正重命名.它们都以"事后"的方式计算:git将一个提交与另一个提交进行比较,并在比较时确定是否存在重命名.这意味着git是否会认为"重命名"是动态变化的.我知道你在问一个你还没有做过的提交,但是请耐心等待,这真的很有帮助(但答案很长).
当您询问git(via git show或git log -por git diff HEAD^ HEAD)"上次提交中发生的事情"时,它会运行前一次提交的差异(HEAD^或者前一次提交HEAD~1的实际原始SHA-1 - 其中任何一种都可以识别它)和当前的commit(HEAD).在制造这种差异的过程中,它可能会发现曾经存在old.txt并且不再存在; 没有,new.txt但现在有.
这些文件名 - 曾经存在但未存在的文件,以及现在没有的文件 - 被放入标记为"重命名的候选者"的堆中.然后,对于堆中的每个名称,git比较"旧内容"和"新内容".精确匹配的比较非常简单,因为git将内容减少到SHA-1的方式; 如果完全匹配失败,git会切换到可选的"内容至少相似"的差异来检查重命名.与git diff此任选的步骤是通过控制-M标志.使用其他命令,它可以由您的git config值设置,也可以硬编码到命令中.
现在,回到暂存区域git status:在index/staging-area中的git存储基本上是"下一次提交的原型".当你git add做某事时,git会在那时存储文件内容,计算进程中的SHA-1,然后将SHA-1存储在索引中.当你有git rm什么东西时,git会在索引中存储一条说明"在下次提交时故意删除此路径名称".
git status然后,该命令只是做一个差异或两个差异:HEADvs索引,用于将要提交的内容; 和索引与工作树,可能(但尚未)将被提交.
在第一个差异中,git使用与始终相同的机制来检测重命名.如果HEAD提交中的路径在索引中消失,并且索引中的路径是新的而不在HEAD提交中,则它是重命名检测的候选者.该git status命令硬连线检测改名为"on"(文件数限制到200;只有一个候选人,重命名检测这个限制是很多).
这对你的案子意味着什么?好吧,你重命名了一个文件(没有使用git mv,但它并不重要,因为git status找到了重命名,或者没有及时找到它git status),现在有一个更新,不同版本的新文件.
如果您git add是新版本,那么较新的版本会进入repo,并且其SHA-1位于索引中,并且git status差异何时会比较新旧版本.如果它们至少是"50%相似"(硬连线值git status),git会告诉你文件被重命名.
当然,git add-ing的修改内容是不是你要求很什么:你想要做一个中间媒介的提交文件所在的文件仅重命名,即用新名称的树,但旧的内容提交.
你不必有这样做,因为所有的上述动态重命名检测的.如果你想这样做(无论出于何种原因)......好吧,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
Run Code Online (Sandbox Code Playgroud)
我们现在做的是,首先,手动将文件放回旧名称,然后git mv再次切换到新名称:
$ mv new-name.txt old-name.txt
$ git mv old-name.txt new-name.txt
Run Code Online (Sandbox Code Playgroud)
这次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
Run Code Online (Sandbox Code Playgroud)
现在只是git commit为了进行重命名提交,而不是新内容.
(请注意,这取决于没有旧文件的新文件!)
怎么用git update-index?好吧,首先让我们回到"工作树中的变化,索引匹配HEAD提交"状态:
$ git reset --mixed HEAD # set index=HEAD, leave work-tree alone
Run Code Online (Sandbox Code Playgroud)
现在让我们看看索引中的内容old-name.txt:
$ git ls-files --stage -- old-name.txt
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt
Run Code Online (Sandbox Code Playgroud)
因此,我们需要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
Run Code Online (Sandbox Code Playgroud)
(注意:我为了发布目的打破了上面的内容,当我输入它时它就是一行;在sh/bash中,它应该像这样分解,给定我添加的反斜杠以继续"sed"命令) .
还有其他一些方法可以做到这一点,但只是简单地提取索引条目两次并将第一个修改为删除,第二个使用新名称似乎是最简单的,因此sed命令.第一次替换改变文件模式(100644,但任何模式将变为全零)和SHA-1(匹配任何SHA-1,替换为git的特殊全零SHA-1),第二次替换模式和替换名称时单独使用SHA-1.
当update-index完成时,索引记录了旧路径的删除和新路径的添加(使用与旧路径中相同的模式和SHA-1).
请注意,如果索引具有未合并的条目,old-name.txt则可能会严重失败,因为文件可能还有其他阶段(1到3).
Rhu*_*arb 11
@torek给出了一个非常明确和完整的答案.那里有很多非常有用的细节; 这是非常值得正确阅读.
但是,为了那些匆忙的人,给出的非常简单的解决方案的关键是:
我们现在做的是,首先,手动将文件放回旧名称,然后使用git mv再次切换到新名称:
Run Code Online (Sandbox Code Playgroud)$ mv new-name.txt old-name.txt $ git mv old-name.txt new-name.txt
(这只是mv我失踪的背后,才有git mv可能.)
如果您觉得有用,请upvote @ torek的答案.