如果 rebase 分支删除了最近提交中添加的文件,为什么 git rebase 会删除该文件?
Why does git rebase delete a file added in the most recent commit if it was deleted by the rebase branch?
我想弄清楚为什么 git 变基会导致新创建的文件在我要变基的分支被删除时被删除。例如:
A1 - A2 - A3
\
B1
A2 = add a new file test.txt
A3 = delete test.txt
B1 = add the exact same file as A2
如果B1签出,我执行git rebase A3
,test.txt还是被删除了。我希望结果是:
A1 - A2 - A3 - B1
这意味着 test.txt 仍然存在。为什么变基后 test.txt 被删除了?
Note that any commits in HEAD which introduce the same textual changes as a commit in HEAD..<upstream> are omitted (i.e., a patch already accepted upstream with a different commit message or timestamp will be skipped).
在您的案例中 B1
引入与 A2
相同的更改。因此,当您进行变基时,变基过程中会省略 B1
,因为 已经有了该补丁。您可以添加 -i
选项来进行交互式变基。这让你看到,B1
没有列在变基过程的待办事项列表中。虽然,您可以通过在交互式 rebase 的待办事项列表中添加 pick B1
来手动选择该提交。
哇,这太难了! :-)
使用你的脚本,我重现了这个问题。虽然这一切都有些奇怪,所以首先,我删掉了变基步骤,留下这个(稍微修改过的)脚本:
#!/bin/sh
set -e
if [ -d testing_git ]; then
echo test dir testing_git already exists - halting
exit 1
fi
mkdir testing_git
cd testing_git
git init
touch main.txt
git add .
git commit -m "initial commit"
# setup B branch
git checkout -b B
echo hello > test.txt
git add .
git commit -m "added test.txt"
# setup master
git checkout master
echo hello > test.txt
git add .
git commit -m "added test.txt"
rm test.txt
git add .
git commit -m "remove test.txt"
一旦 运行,检查提交,我得到这个:
$ git log --graph --decorate | sed 's/@/ /'
* commit 249e4893ea7458f45fe5cdc496ddc0292a3f03ef (HEAD -> master)
| Author: Chris Torek <chris.torek gmail.com>
| Date: Thu May 5 20:28:02 2016 -0700
|
| remove test.txt
|
* commit a132dc9e3939b5338f7c784c58da9c83f4902c8d (B)
| Author: Chris Torek <chris.torek gmail.com>
| Date: Thu May 5 20:28:02 2016 -0700
|
| added test.txt
|
* commit 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a
Author: Chris Torek <chris.torek gmail.com>
Date: Thu May 5 20:28:02 2016 -0700
initial commit
注意 master
的父提交是分支 B
的提交,只有三个提交,而不是四个。当脚本 运行 有四个 git commit
命令时,这怎么可能?
现在让我们将 sleep 2
添加到脚本中,紧跟在 git checkout master
之后,然后重新 运行 看看会发生什么...
[edit]
$ sh testrebase.sh
[snip output]
$ cd testing_git && git log --oneline --decorate --graph --all
* cddbff1 (HEAD -> master) remove test.txt
* c4ac1b2 added test.txt
| * fefc150 (B) added test.txt
|/
* 8c07bb6 initial commit
哇哦,现在我们有四个提交和一个正确的分支!
为什么第一个脚本进行了 3 次提交,添加 sleep 2
将其更改为进行 4 次提交?
答案在于提交的身份。每个提交都有一个(据说!)唯一 ID,它是提交内容的校验和。这是第一次在 B
-branch 提交中的内容:
$ git cat-file -p B | sed 's/@/ /'
tree c3cd0188a6a1490204e25547986e49b0b445dec8
parent 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a
author Chris Torek <chris.torek gmail.com> 1462505282 -0700
committer Chris Torek <chris.torek gmail.com> 1462505282 -0700
added test.txt
我们有 tree
、parent
、作者和提交者的两个(姓名、电子邮件、时间戳)三元组、一个空行和日志消息。父级是 master 分支上的第一个提交,树是我们添加 test.txt
(及其内容)时创建的树。
然后,当我们在 master
分支上进行 second 提交时,git 从新文件创建了一个新树。这棵树与我们刚刚在分支 B
上提交的树完全相同,因此它具有相同的唯一 ID(请记住,回购中只有该树的一个副本,因此这是正确的行为) .然后它像往常一样用我的名字、电子邮件和时间戳以及日志消息创建了一个新的提交 object。但是这次提交与我们刚刚在分支 B
上所做的提交完全相同,因此我们获得了与之前相同的 ID,并使分支 master
指向该提交。
换句话说,我们重新使用了提交。我们只是在不同的分支上创建它(因此 master
指向与 B
相同的提交)。
添加 sleep 2
更改了新提交的 时间戳 。现在这两个提交(在 B
和 master
中)不再是逐位相同的:
$ git cat-file -p B | sed 's/@/ /' > bx
$ git cat-file -p master^ | sed 's/@/ /' > mx
$ diff bx mx
3,4c3,4
< author Chris Torek <chris.torek gmail.com> 1462505765 -0700
< committer Chris Torek <chris.torek gmail.com> 1462505765 -0700
---
> author Chris Torek <chris.torek gmail.com> 1462505767 -0700
> committer Chris Torek <chris.torek gmail.com> 1462505767 -0700
不同的时间戳 = 不同的提交 = 更合理的设置。
虽然实际上执行了变基,但还是删除了文件!
原来这是设计使然。当您 运行 git rebase
时,设置代码不会简单地列出每个提交以进行挑选,而是使用 git rev-list --right-only
来查找它应该删除的提交。1
由于添加 test.txt
的提交在上游,Git 完全删除它:这里的假设是你将它发送给上游的某人,他们已经接受了它,并且有不用再拿了。
让我们再次修改复制器脚本——这次我们可以去掉sleep 2
,加快速度——这样就可以把master
不同,不会通过 --cherry-pick --right-only
从列表中删除。我们仍将使用相同的单行添加 test.txt
,但我们还将在该提交中修改 main.txt
:
# setup master
git checkout master
echo hello > test.txt
echo and also slight difference >> main.txt
git add .
git commit -m "added test.txt"
我们可以继续并打开最后的 git checkout B
和 git rebase master
行,这一次,变基按我们最初的预期工作:
$ git log --oneline --decorate --graph --all
* c31b13a (HEAD -> B) added test.txt
* da2ca52 (master) remove test.txt
* 6972019 added test.txt
* 0f0d2e8 initial commit
$ ls
main.txt test.txt
我没有意识到 rebase 会这样做;这不是我所期望的(尽管正如其他答案所指出的那样,它 是 记录的),这意味着说 "rebase is just repeated cherry-pick" 不太正确:它重复了 cherry-选择,具有丢弃提交的特殊情况。
1实际上,对于非交互式rebase,它使用了这个非凡的位:
git format-patch -k --stdout --full-index --cherry-pick --right-only \
--src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
"$revisions" ${restrict_revision+^$restrict_revision} \
>"$GIT_DIR/rebased-patches"
其中 $revisions
在本例中扩展为 master...B
。
git format-patch
的 --cherry-pick --right-only
选项未记录;必须知道在 git rev-list
文档中查找它们。
Interactive rebase 使用不同的技术,但仍然选择掉上游中已经存在的任何提交。如果您将 rebase
更改为 rebase -i
,则会出现这种情况,因为变基指令由一个 noop
行组成,而不是预期的单个 pick
行。
我想弄清楚为什么 git 变基会导致新创建的文件在我要变基的分支被删除时被删除。例如:
A1 - A2 - A3
\
B1
A2 = add a new file test.txt
A3 = delete test.txt
B1 = add the exact same file as A2
如果B1签出,我执行git rebase A3
,test.txt还是被删除了。我希望结果是:
A1 - A2 - A3 - B1
这意味着 test.txt 仍然存在。为什么变基后 test.txt 被删除了?
Note that any commits in HEAD which introduce the same textual changes as a commit in HEAD..<upstream> are omitted (i.e., a patch already accepted upstream with a different commit message or timestamp will be skipped).
在您的案例中 B1
引入与 A2
相同的更改。因此,当您进行变基时,变基过程中会省略 B1
,因为 -i
选项来进行交互式变基。这让你看到,B1
没有列在变基过程的待办事项列表中。虽然,您可以通过在交互式 rebase 的待办事项列表中添加 pick B1
来手动选择该提交。
哇,这太难了! :-)
使用你的脚本,我重现了这个问题。虽然这一切都有些奇怪,所以首先,我删掉了变基步骤,留下这个(稍微修改过的)脚本:
#!/bin/sh
set -e
if [ -d testing_git ]; then
echo test dir testing_git already exists - halting
exit 1
fi
mkdir testing_git
cd testing_git
git init
touch main.txt
git add .
git commit -m "initial commit"
# setup B branch
git checkout -b B
echo hello > test.txt
git add .
git commit -m "added test.txt"
# setup master
git checkout master
echo hello > test.txt
git add .
git commit -m "added test.txt"
rm test.txt
git add .
git commit -m "remove test.txt"
一旦 运行,检查提交,我得到这个:
$ git log --graph --decorate | sed 's/@/ /'
* commit 249e4893ea7458f45fe5cdc496ddc0292a3f03ef (HEAD -> master)
| Author: Chris Torek <chris.torek gmail.com>
| Date: Thu May 5 20:28:02 2016 -0700
|
| remove test.txt
|
* commit a132dc9e3939b5338f7c784c58da9c83f4902c8d (B)
| Author: Chris Torek <chris.torek gmail.com>
| Date: Thu May 5 20:28:02 2016 -0700
|
| added test.txt
|
* commit 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a
Author: Chris Torek <chris.torek gmail.com>
Date: Thu May 5 20:28:02 2016 -0700
initial commit
注意 master
的父提交是分支 B
的提交,只有三个提交,而不是四个。当脚本 运行 有四个 git commit
命令时,这怎么可能?
现在让我们将 sleep 2
添加到脚本中,紧跟在 git checkout master
之后,然后重新 运行 看看会发生什么...
[edit]
$ sh testrebase.sh
[snip output]
$ cd testing_git && git log --oneline --decorate --graph --all
* cddbff1 (HEAD -> master) remove test.txt
* c4ac1b2 added test.txt
| * fefc150 (B) added test.txt
|/
* 8c07bb6 initial commit
哇哦,现在我们有四个提交和一个正确的分支!
为什么第一个脚本进行了 3 次提交,添加 sleep 2
将其更改为进行 4 次提交?
答案在于提交的身份。每个提交都有一个(据说!)唯一 ID,它是提交内容的校验和。这是第一次在 B
-branch 提交中的内容:
$ git cat-file -p B | sed 's/@/ /'
tree c3cd0188a6a1490204e25547986e49b0b445dec8
parent 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a
author Chris Torek <chris.torek gmail.com> 1462505282 -0700
committer Chris Torek <chris.torek gmail.com> 1462505282 -0700
added test.txt
我们有 tree
、parent
、作者和提交者的两个(姓名、电子邮件、时间戳)三元组、一个空行和日志消息。父级是 master 分支上的第一个提交,树是我们添加 test.txt
(及其内容)时创建的树。
然后,当我们在 master
分支上进行 second 提交时,git 从新文件创建了一个新树。这棵树与我们刚刚在分支 B
上提交的树完全相同,因此它具有相同的唯一 ID(请记住,回购中只有该树的一个副本,因此这是正确的行为) .然后它像往常一样用我的名字、电子邮件和时间戳以及日志消息创建了一个新的提交 object。但是这次提交与我们刚刚在分支 B
上所做的提交完全相同,因此我们获得了与之前相同的 ID,并使分支 master
指向该提交。
换句话说,我们重新使用了提交。我们只是在不同的分支上创建它(因此 master
指向与 B
相同的提交)。
添加 sleep 2
更改了新提交的 时间戳 。现在这两个提交(在 B
和 master
中)不再是逐位相同的:
$ git cat-file -p B | sed 's/@/ /' > bx
$ git cat-file -p master^ | sed 's/@/ /' > mx
$ diff bx mx
3,4c3,4
< author Chris Torek <chris.torek gmail.com> 1462505765 -0700
< committer Chris Torek <chris.torek gmail.com> 1462505765 -0700
---
> author Chris Torek <chris.torek gmail.com> 1462505767 -0700
> committer Chris Torek <chris.torek gmail.com> 1462505767 -0700
不同的时间戳 = 不同的提交 = 更合理的设置。
虽然实际上执行了变基,但还是删除了文件!
原来这是设计使然。当您 运行 git rebase
时,设置代码不会简单地列出每个提交以进行挑选,而是使用 git rev-list --right-only
来查找它应该删除的提交。1
由于添加 test.txt
的提交在上游,Git 完全删除它:这里的假设是你将它发送给上游的某人,他们已经接受了它,并且有不用再拿了。
让我们再次修改复制器脚本——这次我们可以去掉sleep 2
,加快速度——这样就可以把master
不同,不会通过 --cherry-pick --right-only
从列表中删除。我们仍将使用相同的单行添加 test.txt
,但我们还将在该提交中修改 main.txt
:
# setup master
git checkout master
echo hello > test.txt
echo and also slight difference >> main.txt
git add .
git commit -m "added test.txt"
我们可以继续并打开最后的 git checkout B
和 git rebase master
行,这一次,变基按我们最初的预期工作:
$ git log --oneline --decorate --graph --all
* c31b13a (HEAD -> B) added test.txt
* da2ca52 (master) remove test.txt
* 6972019 added test.txt
* 0f0d2e8 initial commit
$ ls
main.txt test.txt
我没有意识到 rebase 会这样做;这不是我所期望的(尽管正如其他答案所指出的那样,它 是 记录的),这意味着说 "rebase is just repeated cherry-pick" 不太正确:它重复了 cherry-选择,具有丢弃提交的特殊情况。
1实际上,对于非交互式rebase,它使用了这个非凡的位:
git format-patch -k --stdout --full-index --cherry-pick --right-only \
--src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \
"$revisions" ${restrict_revision+^$restrict_revision} \
>"$GIT_DIR/rebased-patches"
其中 $revisions
在本例中扩展为 master...B
。
git format-patch
的 --cherry-pick --right-only
选项未记录;必须知道在 git rev-list
文档中查找它们。
Interactive rebase 使用不同的技术,但仍然选择掉上游中已经存在的任何提交。如果您将 rebase
更改为 rebase -i
,则会出现这种情况,因为变基指令由一个 noop
行组成,而不是预期的单个 pick
行。