Git更改rebase

4

我有三个分支:master、feature和bugfix... 提交记录看起来像这样:

    4-5-6(feature)
    |
1-2-3(master)
    |
    7(bugfix)

我使用了“git rebase bugfix feature”命令,以便测试我的功能与bugfix的兼容性。

    1-2-3(master)
        |
        7(bugfix)-4-5-6(feature)

现在我需要对我的功能分支进行变基并创建一个拉取请求,但不包含bug修复。所以我执行了"git rebase master feature",期望的结果是:

1-2-3(master)-4-5-6(feature)
    |
    7(bugfix)

相反,它说该功能与主分支保持最新状态。这是正确的,但我不想将提交7合并到其中。 我可以进行交互式变基并删除该提交,但我想知道是否有更好的方法来解决这个问题。 我认为变基只会将一个分支中的提交带到另一个分支,但看起来并非如此。

2个回答

2

需要注意的是 rebase 操作并不会重写历史或移动提交记录,Git 中的提交记录是无法改变的。相反,它会创建新的历史记录并称一直以来都是那样的。例如,当你从以下提交记录开始:

    4-5-6(feature)
    |
1-2-3(master)
    |
    7(bugfix)

而当你执行git rebase bugfix feature命令时,实际上发生的是这样的:
    4-5-6
    |
1-2-3(master)
    |
    7(bugfix)-4A-5A-6A(feature)

三个新的提交被创建,分别是4A、5A和6A。原始的提交还在那里,但没有任何指向它们的内容。它们最终将被清理,但它们会保留一段时间。
这意味着您可以撤消一个 rebase 操作,这就是您想要做的事情。您需要找到在 rebase 之前的 feature。可以使用 git reflog 来完成此操作,它会跟踪每次 HEAD 移动的情况。这发生在 checkout、commit、reset 和 rebase 中。git reflog 可能是这样的:
65e93ca (HEAD -> feature) HEAD@{0}: rebase finished: returning to refs/heads/feature
65e93ca (HEAD -> feature) HEAD@{1}: rebase: 3 feature
6d539a3 HEAD@{2}: rebase: 2 feature
3cd634f HEAD@{3}: rebase: 1 feature
b84924b (bugfix) HEAD@{4}: rebase: checkout bugfix
a9fd2f1 HEAD@{5}: commit: 3 feature
29136bc HEAD@{6}: commit: 2 feature
60543b0 HEAD@{7}: commit: 1 feature
c487530 (master) HEAD@{8}: checkout: moving from master to feature

这告诉我 a9fd2f1 是在 feature 分支被 rebase 之前的最后一次提交。我可以将 feature 分支移回来,而不需要重新做 rebase。

git checkout feature
git reset --hard a9fd2f1

在未来,如果您在做变基之前使用git tag标记功能的原始位置,这样的事情会变得更加容易。然后,您可以使用git reset回到该标记,而无需搜索reflog。
至于您特定的问题,问题在于变基之后您的存储库现在看起来像这样:
6A [feature]
|
5A
|
4A
|
7 [bugfix]
|
3 [master]
|
2
|
1

当你执行git rebase master feature时,Git会注意到master已经是feature的祖先,因此不会进行任何操作。即使bugfix在中间也没有关系。
相反,你需要告诉Git只想要将4A、5A和6A变基,而忽略7。这可以使用--onto语法来实现。
git rebase --onto master bugfix feature

这句话意思是将 bugfixfeature 但不包括bugfix的部分进行变基(rebase)到主分支 master

我建议使用 git reset 而不是尝试重新进行变基。特别是如果有冲突,第二次变基不能保证结果会相同。而使用 git reset 时,显式地回到了该仓库的旧状态。


谢谢!git rebase --onto 正是我所需要的。 如果在 bugfix 上重新基础之后没有进行任何更改,git reflog 将起作用。 - chohocvo

2
我原以为rebase只是将一个分支的提交复制到另一个分支,但实际上不是这样的。这里有个关键:你图表中的提交7在分支feature中,同时也在分支bugfix中,而提交1-2-3则在三个分支中都存在。Git的分支与其他版本控制系统非常不同。一个分支仅通过能够从分支名称指向的提交到达该提交来“包含”一个提交。像master、bugfix和feature这样的分支名称只是指向Git称为分支尖端的“一个特定的”提交。正是这些提交通过每个提交“指回”其前任而形成了链。因此,git rebase实际上是复制提交:您从以下状态开始:
        4--5--6   <-- feature
       /
1--2--3      <-- master
       \
        7      <-- bugfix

to:

        4--5--6   [abandoned - used to be feature]
       /
1--2--3      <-- master
       \
        7      <-- bugfix
         \
          D--E--F   <-- feature

其中D是原始数字4的一个副本,E5的一个副本,F6的一个副本(在此使用第四、五、六个字母,这样我们可以将7复制到G中,如果需要,但这种技术即将失效)。

然而,您仍然可以得到您想要的东西。您只需要再次复制D-E-F,或者——对于这种特殊情况,这可能更好——返回已弃用的原始4-5-6

当您使用git rebase复制提交时,原始提交将保留。您可以通过两个名称找到它们:ORIG_HEADreflog名称。名称ORIG_HEAD会被各种其他命令覆盖,但您可以检查它是否仍指向提交6

git log ORIG_HEAD

您很可能会认出您的原始文件。

reflog名称采用name@{number}的形式,例如,feature@{1}。每当您更改指向name部分的提交时,number部分就会递增,因为Git仅在reflog中保存name的当前值,将其余部分全部上移一位。

因此:

git log feature@{1}

应该显示与git log ORIG_HEAD相同的提交,除了feature@{1}会更长时间保留(随着时间的推移可能变成feature@{2}feature@{3}等)。默认情况下,每个名称的先前值保存至少30天,因此这应该足够的时间来找回它。

要找回它,请使用git reflog feature查看哪个数字适用于@{...}部分,然后在feature上(git checkout feature)运行:

git reset --hard feature@{1}

或者无论是什么数字(不过最好先通过 git log 再次验证)。

(这假设您没有任何需要检查的内容,即 git status 显示一切都干净,因为 git reset --hard 会清除尚未检入的索引和工作树更改。)


如果我在第一次变基之后进行了更改,那么这种方法就行不通了,例如 D--E--F--8 <-- 功能。撤销此操作将不会带走提交 8,我必须单独挑选它。 - chohocvo
你可以使用 git rebase --onto 再次变基,这样可以将重定位的目标(即“放置副本的位置”)与要复制的提交集分开。 (通常由单个分支名称指定。) - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接