我怎样才能做 `git reset --soft <some-commit>` 绕过合并提交?
How can I to do `git reset --soft <some-commit>` bypassing commits with merge?
我有两个分支 - master
和 some
:
C2---M3---C4---C5 some
/ /
C1---C3---C6---C7 master
其中 C1
、C2
... - 它是提交并且 M3
- 合并提交 C3
.
我想做:
git checkout some
git reset --soft C1
但我想绕过合并提交 M3
。
我该怎么做?
因此,您需要来自提交 C2
、C4
和 C5
的更改,而不是来自合并 M3
的更改。我会用一个新的分支来做到这一点:
# Create a new branch started at C1
git checkout -b some-new C1
# Cherry-pick commits
git cherry-pick C2 C4 C5
# Delete the old branch and rename the new
git branch -D some
git branch -m some
首先,让我们注意提交是(完全)read-only 并且(大部分)是永久的。给定一个像这样的提交图片段:
C2---M3---C4---C5 <-- some
/ /
C1---C3---C6---C7 <-- master
无论 reset
-ing 我们做什么,这些相同的提交将继续存在,至少会持续一段时间。如果我们添加一个也指向提交 C5
的保存名称(分支或标记名称),那么无论我们对名称 some
做什么,我们仍然可以命名提交 C5
, C4
、M3
和 C2
。
其次,请记住每次提交都会存储一个完整的、独立的快照。这包括合并提交 M3
。我们可以通过 运行ning git diff
将合并提交变成一组更改,以将快照的内容与提交的 parent 的内容进行比较——但是对于合并提交,它具有两个 parent,我们要从两个parent中选一个。
第三,既然你提到了git reset --soft
,让我们注意,除了提交之外,Git给了我们一个work-tree我们实际工作(并可以查看提交),以及一个 index——也称为 staging area 或 cache ——我们在提交之前用来构建每个提交。如果我们 运行:
git checkout some
Git 将从名称 some
当前指向的提交内容中填充索引和 work-tree,即 C5
,并附上我们的 HEAD
到名字 some
。让我们附加第二个名字,tmp
,此时,使用 git branch tmp
:
C2---M3---C4---C5 <-- some (HEAD), tmp
/ /
C1---C3---C6---C7 <-- master
运行 git reset --soft <hash-of-C1>
此时将使名称 some
指向提交 C1
,同时保持我们的索引和 work-tree 不变。也就是说,索引和 work-tree 内容将继续匹配 C5
:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
.
... <-------------- some (HEAD)
如果我们现在进行新的提交,通过 运行ning git commit
,我们将得到一个新的提交 C8
,其内容与 C5
的内容匹配。这是因为新的提交是根据索引的内容进行的,它与 C5
的内容相匹配。 C8
的 parent 将是 C1
,然而,给我们:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
\
C8 <-------------- some (HEAD)
之后 git diff some tmp
将完全没有区别。
你在评论中提到你想要的,在索引中或者可能作为新提交 C8
,是 content 可以通过 cherry-picking 实现C2
、C4
和 C5
在提交 C1
之上。你不能单独使用 git reset
来获得它。
由 cherry-picking
构建
最直接的方法是设置一个分支名称指向提交C1
,同时设置索引和work-tree匹配C1
:
git checkout -b new <hash-of-C1>
然后使用 git cherry-pick
,可选地使用 -n
,如 ,我在输入时输入。您也可以使用 git reset --hard
,而不是 git reset --soft
,移动 some
指向 C1
,同时保留提交名称 C5
(如上图中使用 tmp
)。然后 name 您构建的新分支,通过 cherry-picking 三个所需的提交,将是 some
.
通过还原构建
最后,如果您愿意,您可以尝试通过 subtractive 过程构建您的新提交。这可能有点容易出错,因为它取决于合并的内容 M3
,1 但它的工作原理如下:
我们知道C5
实际上就是C1
加上C2
-as-changeset加上C1
-vs -C3
为changeset-via-merge,加上C4
-as-changeset,加上C5
-as-changeset.
我们可以通过 git diff
-ing 直接计算 C1
-vs-C3
。
更好:我们可以计算 C1-vs-C3
间接 ,通过 git diff
ing C2
与 M3
.这将处理某些重复情况,其中 C1
-vs-C3
与 C1
-vs-C2
具有 相同的 更改, 因此它们在 C2
-vs-M3
.
中不是 doubly-included
我们可以(至少尝试)随时reverse-apply任何我们喜欢的补丁。也就是说,将提交变成变更集(通过与 parent 进行比较),而不是将这些更改复制到 没有 的某个提交中,我们可以 撤消 某些提交中 确实 的更改。执行此操作的命令是 git revert
.
然后,假设我们像以前一样检查提交 some
,以便我们只有初始设置:
C2---M3---C4---C5 <-- some (HEAD)
/ /
C1---C3---C6---C7 <-- master
现在我们运行git revert -m 1 <hash-of-M3>
。这告诉 Git 比较 C2
与 M3
以查看发生了什么变化。2 结果是一个新的提交 C8
:
C2---M3---C4---C5---C8 <-- some (HEAD)
/ /
C1---C3---C6---C7 <-- master
其中很可能包含您想要的 内容:C1
作为快照加上 C2
作为变更集以获取 C2
作为内容,加上 M3
vs C2
作为变更集以获取 M3
作为内容(但最终 minus M3
vs C2
作为最后的变更集),等等。由于 C8
un-does M3
做了什么,C8
应该有想要的内容。
此时你可以,如果你愿意,git reset --soft <hash-of-C1>
,留下索引,work-tree设置你想要的方式,然后运行 git commit
创建提交C9
:
C2---M3---C4---C5---C8
/ /
C1---C3---C6---C7 <-- master
\
C9 <-------------- some (HEAD)
由于没有 name 可以找到它们,这里图表顶行的所有提交都变得不可见,大约 30 天后,Git当它 运行 的垃圾收集器真正删除它们时。
1特别是,如果我们使用 git revert <hash-of-C3>
,我们可能会还原太多。这就是我们 git revert -m 1 <hash-of-M3>
在这里的原因。
2这假设 M3
的第一个 parent 实际上是 C2
。在任何正常的提交增长过程中,它都会,但它值得 double-checking.
我喜欢 cherry-picking 方法,但另一种方法是:
git checkout some
git rebase --interactive --preserve-merges C2
...然后编辑呈现的选择列表,将合并提交 M3 行前面的 pick
更改为 drop
。您最终会得到 C5->C4->C2
作为 some
上的一组提交,这些提交也不在 master
.
中
我有两个分支 - master
和 some
:
C2---M3---C4---C5 some
/ /
C1---C3---C6---C7 master
其中 C1
、C2
... - 它是提交并且 M3
- 合并提交 C3
.
我想做:
git checkout some
git reset --soft C1
但我想绕过合并提交 M3
。
我该怎么做?
因此,您需要来自提交 C2
、C4
和 C5
的更改,而不是来自合并 M3
的更改。我会用一个新的分支来做到这一点:
# Create a new branch started at C1
git checkout -b some-new C1
# Cherry-pick commits
git cherry-pick C2 C4 C5
# Delete the old branch and rename the new
git branch -D some
git branch -m some
首先,让我们注意提交是(完全)read-only 并且(大部分)是永久的。给定一个像这样的提交图片段:
C2---M3---C4---C5 <-- some
/ /
C1---C3---C6---C7 <-- master
无论 reset
-ing 我们做什么,这些相同的提交将继续存在,至少会持续一段时间。如果我们添加一个也指向提交 C5
的保存名称(分支或标记名称),那么无论我们对名称 some
做什么,我们仍然可以命名提交 C5
, C4
、M3
和 C2
。
其次,请记住每次提交都会存储一个完整的、独立的快照。这包括合并提交 M3
。我们可以通过 运行ning git diff
将合并提交变成一组更改,以将快照的内容与提交的 parent 的内容进行比较——但是对于合并提交,它具有两个 parent,我们要从两个parent中选一个。
第三,既然你提到了git reset --soft
,让我们注意,除了提交之外,Git给了我们一个work-tree我们实际工作(并可以查看提交),以及一个 index——也称为 staging area 或 cache ——我们在提交之前用来构建每个提交。如果我们 运行:
git checkout some
Git 将从名称 some
当前指向的提交内容中填充索引和 work-tree,即 C5
,并附上我们的 HEAD
到名字 some
。让我们附加第二个名字,tmp
,此时,使用 git branch tmp
:
C2---M3---C4---C5 <-- some (HEAD), tmp
/ /
C1---C3---C6---C7 <-- master
运行 git reset --soft <hash-of-C1>
此时将使名称 some
指向提交 C1
,同时保持我们的索引和 work-tree 不变。也就是说,索引和 work-tree 内容将继续匹配 C5
:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
.
... <-------------- some (HEAD)
如果我们现在进行新的提交,通过 运行ning git commit
,我们将得到一个新的提交 C8
,其内容与 C5
的内容匹配。这是因为新的提交是根据索引的内容进行的,它与 C5
的内容相匹配。 C8
的 parent 将是 C1
,然而,给我们:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
\
C8 <-------------- some (HEAD)
之后 git diff some tmp
将完全没有区别。
你在评论中提到你想要的,在索引中或者可能作为新提交 C8
,是 content 可以通过 cherry-picking 实现C2
、C4
和 C5
在提交 C1
之上。你不能单独使用 git reset
来获得它。
由 cherry-picking
构建最直接的方法是设置一个分支名称指向提交C1
,同时设置索引和work-tree匹配C1
:
git checkout -b new <hash-of-C1>
然后使用 git cherry-pick
,可选地使用 -n
,如 git reset --hard
,而不是 git reset --soft
,移动 some
指向 C1
,同时保留提交名称 C5
(如上图中使用 tmp
)。然后 name 您构建的新分支,通过 cherry-picking 三个所需的提交,将是 some
.
通过还原构建
最后,如果您愿意,您可以尝试通过 subtractive 过程构建您的新提交。这可能有点容易出错,因为它取决于合并的内容 M3
,1 但它的工作原理如下:
我们知道
C5
实际上就是C1
加上C2
-as-changeset加上C1
-vs -C3
为changeset-via-merge,加上C4
-as-changeset,加上C5
-as-changeset.我们可以通过
git diff
-ing 直接计算C1
-vs-C3
。更好:我们可以计算
C1-vs-C3
间接 ,通过git diff
ingC2
与M3
.这将处理某些重复情况,其中C1
-vs-C3
与C1
-vs-C2
具有 相同的 更改, 因此它们在C2
-vs-M3
. 中不是 doubly-included
我们可以(至少尝试)随时reverse-apply任何我们喜欢的补丁。也就是说,将提交变成变更集(通过与 parent 进行比较),而不是将这些更改复制到 没有 的某个提交中,我们可以 撤消 某些提交中 确实 的更改。执行此操作的命令是
git revert
.
然后,假设我们像以前一样检查提交 some
,以便我们只有初始设置:
C2---M3---C4---C5 <-- some (HEAD)
/ /
C1---C3---C6---C7 <-- master
现在我们运行git revert -m 1 <hash-of-M3>
。这告诉 Git 比较 C2
与 M3
以查看发生了什么变化。2 结果是一个新的提交 C8
:
C2---M3---C4---C5---C8 <-- some (HEAD)
/ /
C1---C3---C6---C7 <-- master
其中很可能包含您想要的 内容:C1
作为快照加上 C2
作为变更集以获取 C2
作为内容,加上 M3
vs C2
作为变更集以获取 M3
作为内容(但最终 minus M3
vs C2
作为最后的变更集),等等。由于 C8
un-does M3
做了什么,C8
应该有想要的内容。
此时你可以,如果你愿意,git reset --soft <hash-of-C1>
,留下索引,work-tree设置你想要的方式,然后运行 git commit
创建提交C9
:
C2---M3---C4---C5---C8
/ /
C1---C3---C6---C7 <-- master
\
C9 <-------------- some (HEAD)
由于没有 name 可以找到它们,这里图表顶行的所有提交都变得不可见,大约 30 天后,Git当它 运行 的垃圾收集器真正删除它们时。
1特别是,如果我们使用 git revert <hash-of-C3>
,我们可能会还原太多。这就是我们 git revert -m 1 <hash-of-M3>
在这里的原因。
2这假设 M3
的第一个 parent 实际上是 C2
。在任何正常的提交增长过程中,它都会,但它值得 double-checking.
我喜欢 cherry-picking 方法,但另一种方法是:
git checkout some
git rebase --interactive --preserve-merges C2
...然后编辑呈现的选择列表,将合并提交 M3 行前面的 pick
更改为 drop
。您最终会得到 C5->C4->C2
作为 some
上的一组提交,这些提交也不在 master
.