首先,让我们注意提交是(完全)只读和(大部分)永久的。给定一个像这样的提交图片段:
C2---M3---C4---C5 <-- some
/ /
C1---C3---C6---C7 <-- master
无论reset-ing 我们做什么,这些相同的提交将继续存在,至少在一段时间内。如果我们添加一个保存名称(分支或标签名称)也指向提交 C5,那么无论我们对名称 some 做什么,我们仍然可以将提交命名为 C5、C4、@987654328 @ 和 C2。
其次,记住每个提交都存储一个完整的、独立的快照。这包括合并提交M3。我们可以通过运行 git diff 将快照的内容与提交的父项的内容进行比较,将合并提交转换为一组更改——但是对于具有 两个 父项的合并提交,我们必须选择两个父母之一。
第三,既然你提到了git reset --soft,请注意,除了提交之外,Git 为我们提供了一个 工作树,我们在其中进行实际工作(并且可以查看提交),还有一个 index——也称为 staging area 或 cache——我们用来在每次提交之前构建它。如果我们运行:
git checkout some
Git 将从名称 some 当前指向的提交内容中填充索引和工作树,即 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,同时保持我们的索引和工作树不变。也就是说,索引和工作树内容将继续匹配C5:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
.
... <-------------- some (HEAD)
如果我们现在进行新的提交,通过运行git commit,我们会得到一个新的提交C8,其内容与C5 的内容匹配。这是因为新的提交是根据索引的内容进行的,它与C5 的内容相匹配。 C8 的父级将是 C1,但是,给我们:
C2---M3---C4---C5 <-- tmp
/ /
C1---C3---C6---C7 <-- master
\
C8 <-------------- some (HEAD)
之后git diff some tmp 将完全没有区别。
您在 cmets 中提到,您想要的,在索引中或者可能作为新提交 C8,是 content,可以通过挑选 C2、C4 和C5 在提交 C1 之上。单独使用git reset 无法获得此信息。
通过采摘构建
最直接的方法是设置分支名称指向提交C1,同时设置索引和工作树以匹配C1:
git checkout -b new <hash-of-C1>
然后使用git cherry-pick,可选择使用-n,如phd's answer,在我输入时输入。您也可以使用git reset --hard,而不是git reset --soft,移动@987654367 @ 指向C1,同时保留提交名称C5(如上图中使用tmp)。然后,您构建的新分支的 name,通过挑选三个所需的提交,将是 some。
通过还原构建
最后,如果您愿意,您可以尝试通过 减法 过程构建您的新提交。这可能有点容易出错,因为它取决于合并 M3,1 的内容,但它的工作原理如下:
我们知道C5 实际上是C1,加上C2-as-changeset,加上C1-vs-C3 作为changeset-via-merge,加上@987654378 @-as-changeset,加上C5-as-changeset。
我们可以通过git diff-ing 直接计算C1-vs-C3。
更好:我们可以通过 git diffing C2 与 M3 间接计算 C1-vs-C3 。这将处理某些重复情况,其中C1-vs-C3 与C1-vs-C2 具有相同 更改,因此它们不会被双重包含在@ 987654391@-vs-M3.
我们可以(至少尝试)反向应用任何我们喜欢的补丁。也就是说,将提交变成变更集(通过与父项进行比较),我们可以撤消,而不是将这些更改复制到没有的提交中确实有它们的提交中的那些更改。执行此操作的命令是 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 与 C2 作为变更集获取 M3 作为内容(但最终 minus M3 vs C2 作为最后的变更集),依此类推。由于C8 un-does M3 做了什么,C8 应该有所需的内容。
此时您可以,如果您愿意,git reset --soft <hash-of-C1>,让索引和工作树按照您想要的方式设置,然后运行 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 的第一个父级实际上是C2。在任何正常的提交增长过程中,都会出现这种情况,但值得仔细检查。