为什么git rebase删除了最新提交中添加的文件(如果它被rebase分支删除)?

max*_*art 3 git rebase git-rebase

我试图弄清楚为什么git rebase会导致新创建的文件被删除,如果我正在重新定义的分支将其删除.例如:

A1 - A2 - A3
 \
  B1

A2 = add a new file test.txt
A3 = delete test.txt
B1 = add the exact same file as A2
Run Code Online (Sandbox Code Playgroud)

如果B1已签出并执行git rebase A3,则test.txt仍会被删除.我希望结果如下:

A1 - A2 - A3 - B1
Run Code Online (Sandbox Code Playgroud)

这意味着test.txt仍然存在.为什么在rebase之后删除了test.txt?

tor*_*rek 6

哇,这是一个艰难的!:-)

使用您的脚本,我重现了这个问题.尽管如此,有一些非常奇怪的事情,所以首先,我删除了rebase步骤,留下了这个(略微修改过的)脚本:

#!/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"
Run Code Online (Sandbox Code Playgroud)

一旦运行,检查提交,我得到这个:

$ 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
Run Code Online (Sandbox Code Playgroud)

请注意,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
Run Code Online (Sandbox Code Playgroud)

哇,现在我们有四个提交,还有一个合适的分支!

为什么第一个脚本进行三次提交,并添加sleep 2更改它以进行四次提交?

答案在于提交的身份.每个提交都有一个(据称是!)唯一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
Run Code Online (Sandbox Code Playgroud)

我们拥有treeparent,两(姓名,电子邮件,时间戳)三元组的作者和提交者,一个空行,和日志信息.父是主分支上的第一个提交,树是我们添加时使用的树test.txt(及其内容).

然后,当我们在分支上进行第二次提交时master,git从新文件中创建了一个新树.这个树与我们刚刚在分支上B提交的那个树有点相同,所以它得到了相同的唯一ID(请记住,repo中只有该树的一个副本,所以这是正确的行为).然后它像往常一样用我的名字和电子邮件和时间戳创建了一个新的提交对象,以及日志消息.但是,这是提交位对位相同,我们只是做分支提交B,所以我们像以前一样得到了相同的ID,并提出分支master指向该承诺.

换句话说,我们重新使用了提交.我们只是在不同的分支上创建它(因此master指向相同的提交B).

添加sleep 2更改了新提交的时间戳.现在两个提交(in Bmaster)不再是逐位相同的:

$ 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
Run Code Online (Sandbox Code Playgroud)

不同的时间戳=不同的提交=更明智的设置.

然而,实际上执行rebase,无论如何都丢弃了文件!

事实证明这是设计的.当您运行时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"
Run Code Online (Sandbox Code Playgroud)

我们可以继续前进并打开决赛git checkout Bgit 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
Run Code Online (Sandbox Code Playgroud)

我没有意识到rebase做到了这一点; 这不是我所期望的(尽管正如另一个答案所指出的那样,它已被记录),这意味着说"rebase只是重复樱桃选择"并不完全正确:它是重复的樱桃选择,特殊情况下放弃提交.


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"
Run Code Online (Sandbox Code Playgroud)

其中$revisions扩大,在这种情况下,master...B.

没有记录的--cherry-pick --right-only选项git format-patch; 我们必须知道git rev-list为他们查看文档.

交互式rebase使用不同的技术,但仍然选择已经在上游的任何提交.如果您更改rebaserebase -irebase指令由noop一行而不是预期的单行组成,则会显示此信息pick.