这是关于Git内部的问题。我在这里使用低级命令而不使用分支。
echo f1 > f1.txt
echo f1 > f1.txt
git add .
git commit -m "first"
...cf178d5
现在我想使用索引和f3.txt
命令创建一个包含一个文件write-tree
的新提交:
$ rm f1.txt f2.txt
$ echo ‘f3 content’ > f3.txt
$ git add .
所以目前索引文件和目录只包含新的f3.txt
文件:
$ git ls-files -s
100644 [some hash] 0 f3.txt
$ ls
f3.txt
所以我将树写入存储库并使用新的提交哈希更新HEAD:
LATEST_TREE_HASH=$( git write-tree )
echo $LATEST_TREE_HASH > .git/HEAD
如果我现在运行git status
我得到:
$ git status
Not currently on any branch.
nothing to commit, working directory clean
如果我现在使用两个文件first
和f1.txt
签出f2.txt
提交:
$ git checkout cf178d5
A f3.txt <--------------- why?
HEAD is now at a27a75a... initial commit
Git工作正常,但我相信它会合并索引中的树而不是覆盖。您可以从git checkout
输出中看到它将f3.txt
视为添加的文件,如果我检查索引文件内容:
$ git ls-files -s
100644 [some hash] 0 f1.txt
100644 [some hash] 0 f2.txt
100644 [some hash] 0 f3.txt
$ ls
f1.txt f2.txt f3.txt
它显示三个文件。有人可以解释一下这种行为吗?
PS。如果您想要提问,因为它与日常的git工作流程无关 - 请不要。
答案 0 :(得分:2)
修改:问题已经改变,足以使之前的回复无效。
还有一个错字(f1.txt
列出两次)和时髦的非ASCII Unicode引号,但我们现在可以看到这里出了什么问题:
$ LATEST_TREE_HASH=$( git write-tree ) $ echo $LATEST_TREE_HASH > .git/HEAD
这有点问题。正如Mark Adelsberger noted in a comment和您的脚本在此处使用TREE
一词所述,git write-tree
会写一个树,而不是提交。
.git/HEAD
中的内容应该是两件事之一:
ref: refs/heads/name
形式的字符串,其中 name
是有效的分支名称,或反过来,分支名称 - refs/heads/name
- 形式的引用必须始终指向提交对象,而不是指向blob,树或标记对象。
这意味着一般的Git假设无论.git/HEAD
出现什么,它都指的是提交对象。通过将此树形哈希写入.git/HEAD
,您违反了此假设。但是,为了允许未出现的分支机构&#34;,例如尚未master
的初始存储库的状态,HEAD
可以包含名称实际上并不存在的分支。
接下来发生的事情,我认为,不能保证。 git checkout
命令假定如果HEAD
包含有效散列,则它包含提交散列,唯一允许的另一种可能性是HEAD
包含孤立分支的名称。因此,我们运行git checkout target_hash
,如您的示例所示:
git checkout cf178d5
假设HEAD
包含有效的提交哈希。让我们将其称为旧哈希,区别于目标提交哈希。在这种情况下,git checkout
会:
显然--force
会禁用该检查,但this is the basic process by which both staged and unstaged modifications are carried from one checkout to another when switching branches without being in a "clean" state。该过程在Two Tree Merge section of the git read-tree
documentation中的所有血腥细节中进行了描述。
规则允许的另一种可能性是您目前在孤儿分支上。在这种情况下,没有当前提交。最有可能的是,Git只使用the empty tree ,就像它是当前的提交一样。然后它遵循案例1的相同规则,现在允许它,因为它有一棵树。
但显然,这不是保证。如果Git要使用存储在.git/HEAD
中的当前(有效)树作为基本树而不是空树,然后按照案例1进行操作,那么您将看到你的两个文件被删除。请关注git read-tree
中列出的所有子案例,其中$H
设置为现有树,而$H
设置为空树。 (我承认没有这样做,但我认为这是行为的来源。但请参阅阅读树文档中关于案例3的评论!)
1 Git实际上是使用存储在index.lock
文件中的临时索引来实现的。如果一切顺利,临时索引将重命名为常规索引,在此过程中解锁索引。如果情况不妙,Git会删除临时index.lock
文件,丢弃临时索引并解锁索引。
还有另一组时髦的非ASCII引号,它们使你的指令剪切和粘贴失败,所以当我做了正常的第一次提交时,我最终得到了两个文件:
$ git commit -m "second commit"
[master (root-commit) b9c7e4b] second commit
2 files changed, 1 insertion(+)
create mode 100644 f3.txt
create mode 100644 f3.txtcontentecho
$ ls
f3.txt f3.txtcontentecho
$ git ls-files -s
100644 5927d85c2470d49403f56ce27afd8f74b1a42589 0 f3.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 f3.txtcontentecho
但请注意,我的ls
vs ls-files -s
输出与您的输出大不相同:
所以目前索引文件和目录只包含新的f3.txt文件:
$ git ls-files -s 100644 [some hash] 0 f3.txt $ ls f1.txt f2.txt
我一点都不清楚你为什么现在在你的工作树中有文件f1.txt
和f2.txt
;我没有。
现在我们使用git commit-tree
创建一个提交并运行git checkout
:
$ INITIAL_COMMIT_HASH=$( \
> echo 'initial commit' | git commit-tree $INITIAL_TREE_HASH )
$ git checkout $INITIAL_COMMIT_HASH
但我得到的是非常不同的:
Note: checking out 'cd1bc16160c8a2814cd94bc8397230ffe5a16c22'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at cd1bc16... initial commit
并且所有内容都按预期读取(文件f1.txt
和f2.txt
位于工作树和索引中; f3
文件都不可见。运行git log --graph --all
显示预期的两个(断开连接)提交(两者都是root提交,没有父项)。