我试图了解Git的一个问题是它如何处理未跟踪的文件,或未跟踪但现在已提交的文件。
以下是一个例子:
echo "one" > one.txt
git add one.txt
git commit -m "#1"
所以,“one.txt”在第一次提交中。现在,我将再创建两个文件,但只添加一个(我将把下一个提交标记为“#2”,以便我们可以返回它):
echo "two" > two.txt
echo "three" > three.txt
git add two.txt
git commit -m "#2"
git tag "#2"
所以,“two.txt”在第二次提交中,“three.txt”就是这个额外的文件,闲逛。然后,我结账HEAD~,果然,Git删除了文件“two.txt”,因为当时不存在...
git checkout HEAD~
ls
___ one.txt three.txt
好的..回到分支的末尾,只有添加第三个文件。所以,我们只是跟踪它。 然后让我们向后退一个提交......
git checkout "#2"
git add three.txt
git checkout HEAD~
ls
___ one.txt three.txt
嗯......好吧......所以只是跟踪一个文件是不足以让Git管理它是否存在。所以,我们再次进入分支的末尾,然后提交,然后备份(两次,这次,回到提交#1),我们看到了什么?
git checkout "#2"
git commit -m "#3"
git checkout HEAD~~
ls
___ one.txt
这一次,Git删除了文件“three.txt”。所以,我的问题是:有人可以描述 Git决定如何做到这一点?它似乎不是简单的文件名是否出现在存储库中的任何树对象中,因为我可以创建一个 new three.txt(一个正在<的文件名) em>跟踪并在存储库中有一个已提交的版本,然后执行更多检查,Git再次像以前那样单独使用它。
有人可以解释一下Git在结账时如何决定删除可以做什么?
答案 0 :(得分:0)
我没有提出我自己的解释,而是会引导你进入优秀的Pro Git book,这是免费的:)。这个链接是相关部分,并很好地解释了各个州。
你的解释涵盖了Git非常全面地关注文件的方式。忘记重置一分钟,Git会将更改应用到工作目录,将其从状态X转到状态Y(使用记录的变更集)。如果你有一个你没有提交的文件,或者自上次提交以来已经改变的文件,Git会将此视为稍后的更改,因此不会撤消未提交的工作,即使该文件不存在于先前的提交。
这是一个非常理智的策略,因为它可以降低失去未提交作品的风险。
如果您想要从工作目录中删除更改并返回干净状态(HEAD提交),您可以执行git checkout -- .
或将.
替换为您的文件/目录选择。
答案 1 :(得分:0)
理解这一点的一个关键是git add
只是将文件放入索引中;执行git checkout
比较&#34;来自&#34;的树。和&#34;到&#34; 提交(如果路径P
存在于&#34;来自&#34;但不是&#34;来自&#34;路径P
是理想的-removed);并且checkout
将通过索引写入工作目录(clobbering index-only changes)。另一个原则是&#34;不要覆盖或删除任何未提交的内容&#34;。
让我们讨论具体案例。你是&#34;在&#34;使用未跟踪的文件three.txt
提交#2。然后你git add three.txt
。它现在在索引中,但未提交,因此它(仍然)不在当前(HEAD
)提交的树中,这是标记为#2
的提交。
现在你要求git检查提交&#34;#1&#34;。比较树:HEAD
解析&#34;#2&#34;,其中包含文件one.txt
和two.txt
。 HEAD~
解析为&#34;#1&#34;,其中包含one.txt
但two.txt
。因此,转换工作树的方法是&#34;删除two.txt
,如果one.txt
不同,则替换它。&#34; (并且,删除需要删除索引条目以及工作树副本,而替换需要通过索引进行写入。)
&#34;确认任何工作都不会被破坏&#34;然后步骤必须检查:
two.txt
和one.txt
的索引版本会发生什么?虽然three.txt
现在在索引中,但是没有必要删除它或更改索引版本,因此它现在只是保留在索引中,并准备好添加到新提交中#&#34; 34。
现在(HEAD
指向#1),您要git checkout #2
。 Git必须重复上述内容,但这次树比较结果是&#34;添加two.txt
,可能会替换one.txt
&#34;。 verify-no-clobbering步骤检查这些是否正常(它们是)并且git执行结帐。 three.txt
的索引条目仍为仍然为&#34;准备好添加到新提交&#34;。
一旦你执行了git commit -m '#3'
,git就会将索引写入树中(这比直接扫描工作目录要容易得多;实际上,索引是一种幻想,中间形式的中途在git repo树条目和工作树之间)并写一个新的提交,并将其作为树。
然后,git checkout
解析为提交#3,然后从那里移动到提交#1,最后HEAD
完成。比较树木,从#3到#1的变化是&#34;删除two.txt
,删除three.txt
,可能会替换one.txt
&#34;。结帐必须验证不会破坏任何工作(它赢了),然后就可以了。
请注意,如果你在git&#39; s&#34;后面乱扔东西,可以通过更改HEAD
点的提交而不改变索引(git symbolic-ref HEAD refs/heads/differentbranch
或{{ 1}}例如),你可以得到一些有趣的效果。这就是我为一个例子所做的事情:
git update-ref HEAD HEAD~
$ git commit -m '#3'
[detached HEAD e3465c4] #3
1 file changed, 1 insertion(+)
create mode 100644 three.txt
$ git tag #3 # since we're "detached", let's save #3
$ git status
$ git status
HEAD detached at #3
$ git update-ref HEAD '#2'
$ git status
HEAD detached from #3
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: three.txt
$ git update-ref HEAD HEAD^
$ git status
HEAD detached from #3
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: three.txt
new file: two.txt
操作不会触及索引,但它会更改update-ref
指向的提交(以及树)。因此,比较index和current-commit-tree会得到不同的结果。