结帐期间中止

Aborts during checkouts

我按时间顺序有以下操作顺序:

mkdir gitcommits
cd gitcommits
echo 'a' > filea.txt
echo 'b' > fileb.txt
git init
git add .
git commit -m "Commit0"

这会在工作树中产生以下内容。

Commit0: shaid0
filea (I remove the .txt here to indicate whether it has changed or not)
fileb

在工作目录中,我更改了 filea.txt 以获得修改后的 filea*:

echo 'a*' > filea.txt

紧随其后的是

git stash

这让我回到 HEAD 指向 Commit0。现在,我将 fileb 修改为 fileb* 因此:

echo 'b*' > fileb.txt

那么,我是这样犯的:

git add fileb.txt
git commit -m "Commit1"

在工作树中获取:

Commit1: shaid1
filea
fileb* (note no .txt suffix to indicate that this is modified fileb.txt)

为此,我应用了存储:

git stash apply

在我的工作目录中获取filea*fileb*。我通过以下方式将它们提交:

git add filea.txt
git commit -m "Commit2" 

其工作树是:

Commit2: shaid2
filea*
fileb*

然后我通过以下方式切换到 Commit1

git checkout HEAD^

这使我进入分离头模式,工作目录内容为:

Commit1: shaid1
filea
fileb*

我现在通过以下方式重新应用存储:

git stash apply

获得:

Working Tree:
filea*
fileb*

从这个位置开始,以下命令失败:

git checkout master

error: Your local changes to the following files would be overwritten by checkout:
        filea.txt
    Please commit your changes or stash them before you switch branches.
    Aborting

但是,从这个职位开始,申请:

git checkout HEAD^

在我回到 Commit0 的意义上起作用。

我的问题是,为什么 git checkout HEAD^ 继续没有错误,而 git checkout master 中止。

Checkout another branch when there are uncommitted changes on the current branch 中,我注意到真正的答案与 Git 的索引发生了什么有关。那么让我们看看会发生什么。

首先,我把你的复制器变成了一个 shell 脚本,它在这里:

#! /bin/sh -e

mkdir gitcommits
cd gitcommits
echo a > filea.txt
echo b > fileb.txt
git init
git add .
git commit -q -m Commit0
echo 'a*' > filea.txt
git stash
echo 'b*' > fileb.txt
git add fileb.txt
git commit -q -m Commit1

git stash apply -q
git add filea.txt
git commit -m Commit2

git switch -q --detach HEAD^
git stash apply -q

echo "expecting failure here"
if git switch -q --detach master; then
        echo "bug? did not fail as expected"
fi

echo "expect success if you:"
echo "    git switch --detach HEAD^"
echo "please explain -- starting subshell now"
sh -i

运行 这让我获得了一个互动 shell:

loginsh$ sh repro.sh
Initialized empty Git repository in [redacted]
Saved working directory and index state WIP on master: 42882c4 Commit0
[master 3e175c7] Commit2
 1 file changed, 1 insertion(+), 1 deletion(-)
expecting failure here
error: Your local changes to the following files would be overwritten by checkout:
    filea.txt
Please commit your changes or stash them before you switch branches.
Aborting
expect success if you:
    git switch --detach HEAD^
please explain -- starting subshell now
$ 

让我们看看在每个 git switchgit checkout 命令上 Git 必须 remove-and-replace 的文件。失败的是切换到 master:

$ git diff --cached master
diff --git a/filea.txt b/filea.txt
index d2a71ae..7898192 100644
--- a/filea.txt
+++ b/filea.txt
@@ -1 +1 @@
-a*
+a

filea.txt必须是removed-and-replaced。但是 git status --short 告诉我们 ...

$ git status --short
 M filea.txt

... filea.txt 中有一些珍贵的作品,不应该被草率地销毁。所以我们得到了我们刚刚看到的错误,我们必须提交或隐藏。

同时,切换到 HEAD^ 将 remove-and-replace 以下文件:

$ git diff --cached HEAD^
diff --git a/fileb.txt b/fileb.txt
index 6178079..b435762 100644
--- a/fileb.txt
+++ b/fileb.txt
@@ -1 +1 @@
-b
+b*

但是 fileb.txt 目前是“干净的”:没有必须保存在某处的更改。所以 Git 认为在更改提交时 remove-and-replace fileb.txt 是安全的。如果我们 运行 git switch 命令会发生这种情况:

$ git switch --detach HEAD^
$ git switch --detach HEAD^
M       filea.txt
Previous HEAD position was a4a91a5 Commit1
HEAD is now at 42882c4 Commit0
$ head *
==> filea.txt <==
a*

==> fileb.txt <==
b
$ 

文件 fileb.txt 对破坏是安全的,被 git switch 对提交 HEAD^ 所做的 tree-reading 破坏,工作树副本也是如此。如果我们强制切换到 master:

$ exit
loginsh$ rm -rf gitcommits/
loginsh$ sh repro.sh
[redacted, but we get the same messages as last time,
just with different commit hash IDs]
$ git show :filea.txt
a
$ cat filea.txt
a*

(请注意,此时 filea.txt 的索引和工作树版本有何不同,这也是 git status --short 向我们展示的单个 M 字符的位置.)

$ git switch -f master
Previous HEAD position was 784e81f Commit1
Switched to branch 'master'
$ git show :filea.txt
a*
$ head *
==> filea.txt <==
a*

==> fileb.txt <==
b*
$ git status --short
$ 

我们发现 filea.txtindex 副本已被开关破坏。 working tree 副本实际上并没有损坏,因为 a* 是 working tree 在切换前后的内容;但是,Git 的消息说 Your local changes to the following files ... 而不是 Your local changes to the following working-tree files

使用 git stash,如果您暂存(复制到索引)特定文件的特定版本,事情会变得更加混乱。 git stash apply 步骤删除了索引副本,但 git stash apply --index 恢复了暂存(索引)版本! git stash pushgit stash save 总是 保存 两者; 恢复(应用或弹出)步骤可能会或可能不会完全删除索引提交。

通常 git 非常努力地不让用户意外丢失工作目录中尚未在版本控制中的任何更改,除非您使用 [=10= 等选项明确告诉它这样做] 或 --force.

根据定义,未暂存的更改不受版本控制,因此当您在最后一步尝试 git checkout master 时,当前文件内容是否与 master 中的内容相同并不重要,它只是检测到您对该文件进行了未暂存的更改,因此甚至无法在此处尝试进行简单的合并。如果您预先使用 git add filea.txt 进行更改,它会在这种特殊情况下起作用,但如果有任何差异,它仍然会失败。 Checkout 不会尝试 'real' 合并。

另一方面 git checkout HEAD^ 在这一点上不是问题。由于文件 filea.txt 没有更改,因此 git 不必触及此文件并且可以保持您的工作更改不变(无论是暂存还是未暂存)。