如何更改共同历史中的提交?

How to change a commit in common history?

假设我有以下历史:

          --E  <- branch y
         /
A---B---C---D  <- branch x

我想修改提交 B,例如通过添加提交 B2 并使用 git rebase -i.

将其压缩为 B

历史应该是这样的:

          --E'  <- branch y
         /
A---B'--C'--D'  <- branch x

我的问题是:我该怎么做?

我尝试在 ED 和变基之后提交它,但这导致:

  --B'--C'--E'  <- branch y
 /       
A---B---C---D  <- branch x

解决方法 是将 E 变基到 D;然后我可以转到 E',添加并压缩 B2,结果是

             -E'  <- branch y
            /
A---B'--C'--D' 
 \
  --B---C---D    <- branch x

在哪里我可以让分支 x 指向 D':

             -E'  <- branch y
            /
A--B'--C'--D'   <- branch x

几乎类似于我想要实现的,但感觉有点hacky因为我手动移动了分支指针. (在我的特殊情况下,变基是可以接受的,但我想知道一般情况。) 如果主要问题没有简单的解决方案,我想知道是否可以在不手动移动分支指针的情况下使用此解决方法。

附加信息:None 的相应提交已被推送。

  1. 在分支 X 上使用 git rebase -i 并编辑您的提交 B

(在提交旁边设置 e,使用 git commit --amend 然后 git rebase --continue

  1. 用分支 X 重新设置分支 Y

(当你在Y分支时,使用git rebase Y)

  1. 删除提交 D

(只需重置最后两次提交和 git cherry-pick E 提交)

How to modify far commit and do not lose following commits

  1. 回滚到 B
  2. 隐式 B2 提交给 B
  3. 应用 C 提交
  4. 缝合到branch_y
  5. 合并新的 B2 和 C 提交
  6. 推送更新的分支
  7. 切换回 branch_x
  8. 应用 D 提交
  9. 使用 force
  10. 推送分支

git reset B_HASH
git commit B2 --amend
git cherry-pick C_HASH

git checkout branch_y
git merge branch_x
git push origin branch_y

git checkout branch_x
git cherry-pick D__HASH
git push origin branch_x -f

更新

附加信息:None 个相应提交已被推送。

如果是这样,那么您不需要使用推送的所有步骤

git commit --amend

Replace the tip of the current branch by creating a new commit. The recorded tree is prepared as usual (including the effect of the -i and -o options and explicit pathspec), and the message from the original commit is used as the starting point, instead of an empty message, when no other message is specified from the command line via options such as -m, -F, -c, etc. The new commit has the same parents and author as the current one (the --reset-author option can countermand this).

git 摘樱桃

Given one or more existing commits, apply the change each one introduces, recording a new commit for each. This requires your working tree to be clean (no modifications from the HEAD commit).

git push -f

Usually, the command refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. Also, when --force-with-lease option is used, the command refuses to update a remote ref whose current value does not match what is expected.

This flag disables these checks, and can cause the remote repository to lose commits; use it with care.

Note that --force applies to all the refs that are pushed, hence using it with push.default set to matching or with multiple push destinations configured with remote.*.push may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterpart). To force a push to only one branch, use a + in front of the refspec to push (e.g git push origin +master to force a push to the master branch). See the ... section above for details.

使用 --fixup--autosquash 加上 rebase --onto:

#!/bin/bash
set -eu

t-a-c () {
    touch ""
    git add ""
    git commit -m ""
}

cd ""
git init

t-a-c A

git checkout -b x
git branch -d master

t-a-c B
t-a-c C

git branch y
t-a-c D

git checkout y
t-a-c E

git checkout @~2
git checkout -b z
echo "B'" > B
git add B
git commit --fixup @

git checkout x
git rebase --onto z z~

git rebase -i --autosquash z~2

git checkout y
git rebase --onto x~ x~2

git branch -D z

git log --oneline --graph --decorate --all

输出:

* a92a035 (HEAD -> y) E
| * d5c169a (x) D
|/  
* 0a2da9c C
* 2b0087e B
* b395d3a A

一个简单的解决方案是首先在其中一个分支上执行交互式变基,进行更改,然后将另一个分支变基到新的 C':

git checkout branch_x

git rebase -i A
# mark commit B as edit, modify it, and complete the rebase

# rebase branch_y to be based on the new C' instead of C
git rebase --onto C' C branch_y

因为我们告诉最初基于 Cbranch_y 重新基于新的 C',所以我们恢复分支的原始分支结构关于修改后的提交.

请注意,我们需要知道 C 和新的 C' 的提交哈希值。在执行第二个 rebase 命令之前,我们必须在第一个分支上的交互式 rebase 之后查找它们。


另一种避免必须知道提交哈希的解决方案是在此处进行保留合并的交互式变基。请注意,这需要您先合并这两个分支。但是我们这样做只是为了Git保持分支关系,所以之后,我们可以再次摆脱那个合并。

所以要做到这一点,我们从任何分支开始,合并另一个分支。然后我们执行一个保留合并的交互式 rebase 来修改提交 B(以我们想要的任何方式)。之后,Git 将正确重播这些分支(包括合并),因此我们最终得到以下历史记录:

A -- B' -- C' -- D' ---- M'  ← branch
            \           /
             \         /
              E' ------

然后我们可以将分支指针移动到更新的提交 D'E',我们就完成了。

git checkout branch_x
git merge branch_y

# merge preserving interactive rebase
git rebase -p -i A
# mark commit B as edit, modify it, and complete the rebase

# we’re now on M', so move branch pointers to the new commits
git update-ref refs/heads/branch_y HEAD^2
git reset --hard HEAD^

历史现在应该是这样的:

A -- B' -- C' -- D'  ← branch_x
            \
             \
              E'  ← branch_y

不幸的是,我们必须在这里手动移动分支指针,因为我们一次只能变基一个分支。所以因为我们已经为另一个分支重新设置了历史记录的基础(因为合并),我们仍然必须移动它的指针。所以我们只接受我们必须在这里手动重置分支(无论如何这并不是一件坏事)。