结帐期间中止
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 switch
或 git 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.txt
的 index 副本已被开关破坏。 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 push
或 git stash save
总是 保存 两者; 恢复(应用或弹出)步骤可能会或可能不会完全删除索引提交。
通常 git 非常努力地不让用户意外丢失工作目录中尚未在版本控制中的任何更改,除非您使用 [=10= 等选项明确告诉它这样做] 或 --force
.
根据定义,未暂存的更改不受版本控制,因此当您在最后一步尝试 git checkout master
时,当前文件内容是否与 master 中的内容相同并不重要,它只是检测到您对该文件进行了未暂存的更改,因此甚至无法在此处尝试进行简单的合并。如果您预先使用 git add filea.txt
进行更改,它会在这种特殊情况下起作用,但如果有任何差异,它仍然会失败。 Checkout 不会尝试 'real' 合并。
另一方面 git checkout HEAD^
在这一点上不是问题。由于文件 filea.txt
没有更改,因此 git 不必触及此文件并且可以保持您的工作更改不变(无论是暂存还是未暂存)。
我按时间顺序有以下操作顺序:
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 switch
或 git 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.txt
的 index 副本已被开关破坏。 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 push
或 git stash save
总是 保存 两者; 恢复(应用或弹出)步骤可能会或可能不会完全删除索引提交。
通常 git 非常努力地不让用户意外丢失工作目录中尚未在版本控制中的任何更改,除非您使用 [=10= 等选项明确告诉它这样做] 或 --force
.
根据定义,未暂存的更改不受版本控制,因此当您在最后一步尝试 git checkout master
时,当前文件内容是否与 master 中的内容相同并不重要,它只是检测到您对该文件进行了未暂存的更改,因此甚至无法在此处尝试进行简单的合并。如果您预先使用 git add filea.txt
进行更改,它会在这种特殊情况下起作用,但如果有任何差异,它仍然会失败。 Checkout 不会尝试 'real' 合并。
另一方面 git checkout HEAD^
在这一点上不是问题。由于文件 filea.txt
没有更改,因此 git 不必触及此文件并且可以保持您的工作更改不变(无论是暂存还是未暂存)。