将提交历史记录隔离到特定分支

时间:2017-01-02 04:09:16

标签: git

说,我有一个空的存储库,只有一个初始提交:

* - Initial commit (master)

然后我在本地创建一个develop分支并对其进行一些提交。但在我将其推送到远程(尚未与master合并)之前,我使用master更新了remote,这也为master分支提供了一些提交,我得到了这个:

*   - Update something (master)
| * - Make some changes to the new feature (develop)
| |
* | - Make some changes (master)
| * - Add new feature (develop)
|/         
* - Initial commit (master)

所以,我的问题是,在我将develop分支推送到远程之后,我怎么能够而且应该如何将developmaster分支合并为一个提交而不复制任何develop分支到master的历史记录,但保留develop分支上的所有历史记录。

3 个答案:

答案 0 :(得分:3)

简短的回答是你不能 - 但你可以能够做你想要的,这里的主要问题是一堆Git做的事情(并谈论)奇怪和不同的事情,技术定义繁琐(人们经常出错,这只会增加混乱)。

很多背景(对不起,它有点长)

让我水平重绘您的提交(这对于StackOverflow上的文本文章更有效),并给它们一个字母的名称:

A--C--E   <-- master
 \
  B--D    <-- develop

也就是说,A是初始提交,CE是当前&#34; on&#34;的另外两个提交。 (可从中找到)分支masterBD是您提交的两个提交,其中&#34; on&#34; (可以从develop找到。

这是一个技巧问题:哪个分支提交Amasterdevelop

这是一个技巧问题,因为在Git中,它位于两个分支上。

分支名称和可达性

事实上,名称 masterdevelop几乎无关紧要。重要的是提交。我向右侧绘制的名称仅指向一个特定提交。我们将一个特定的提交称为分支的 tip ,然后我们和Git使用每个提交中存储的父信息向后工作,以查找先前的提交。

这些父链接,E返回C返回A,或D返回B返回A,仅限单向:从孩子到父母。它们从我们最初期望的方式开始倒退,并且它们确定来自任何给定提交的可达哪些提交。从C开始,我们可以自己(当然)和C联系A;这就是全部。从E开始,我们可以到达ECA - 这是分支master的完整内容,因为master指向提交E。从D开始,我们可以DBA;由于develop指向D,因此该分支的完整内容为develop

但这意味着提交A位于两个分支上。这就是Git的方式:提交在任意数量的分支上 - 有时甚至没有 - 并且分支集合基于 reachability ,通过每个提交中的父链接。 名称仅用于让我们从图中开始,并且除了分支名称之外还有其他名称,例如标记名称。让我们进行两次新的临时提交FG,仅用于说明:

     tag: T
        |
        v
        F--G   <-- tempbranch
       /
A--C--E   <-- master
 \
  B--D    <-- develop

现在可以通过G查找提交tempbranch;提交F可通过其标记T 通过tempbranch找到,提交A-C-E-F-G全部位于tempbranch。现在让我们完全删除名称 tempbranch

     tag: T
        |
        v
        F--G
       /
A--C--E   <-- master
 \
  B--D    <-- develop

现在我们有一个有趣的案例:提交G根本无法访问。仍可通过代码F访问提交T。 Git最终会垃圾收集提交G,让它真正消失。在此之前,如果您已将哈希ID保存在某个地方,您仍然可以查看它,甚至可以“恢复”#34;给它一个分支或标签名称。 (Git还将这些ID保存在名为 reflogs 的内容中。每个引用都有一个 - 例如分支和标记名称 - 以及HEAD的一个。这些保留了默认情况下30天,即使删除了他们的名字。)

如果我们也删除标记T,那么提交F也有资格进行垃圾回收,最终我们回到之前的五次提交。同时,由于FG不是可见,你不会看到它们:Git会表现得好像他们不在那里,除非你挖掘以某种方式提高他们的哈希ID。

合并提交

尽管如此,让我们来看看常规的普通合并。至少在Git中的合并提交就像任何其他提交一样,但有一个例外:它有两个或更多个父项,而不是一个。 (大多数合并只有两个,并且对于三个或更多的父合并没有什么特别有用。)

让我们看看在提交图方面将develop合并到master的常规合并会发生什么:

A--C--E--F   <-- master
 \      /
  B--D-´   <-- develop

名称develop继续指向提交D,但名称master现在指向新的合并提交 F。提交F点回两者提交E D

当Git进行可达性计算时,它遵循 all 父链接(同时,就像它一样)。所以在这一点上,图中的每个提交都在分支master上。提交A-B-D(仍然)在develop上,如果您git checkout develop并编写提交,则新提交将仅在develop上, name develop自动转移到指向新提交:

A--C--E--F   <-- master
 \      /
  B--D-´--G   <-- develop

这是处理这些事情的常用方法。与提交F一起使用的(工作树副本)将BD的更改合并到master中:Git接受提交{ {1}} vs提交A以查找EmasterA之间的更改,以查找D的更改,并将它们组合在一起develop。然后,它将F E记录为D的父项,以便合并记住合并了哪个提交。

壁球&#34;合并&#34; (背景结束)

尽管如此,我们现在可以谈论Git所谓的&#34;壁球合并&#34;。你可以通过运行F来获得这些,然后再做一些。具体来说,您之后还必须运行git merge --squash

壁球合并实际上根本不是合并。如果我们绘制后效应,我们得到这个图:

git commit

提交A--C--E--F <-- master \ B--D <-- develop 内容与真实合并的内容相同。像往常一样,新提交继续F,即master被移动到指向新提交。关键区别在于新提交指向合并提交,但指向master的上一个提示。

起初,这似乎正是你想要的。然而,它确实有一个非常大的缺点。让我们为master添加一些新的提交:

develop

现在,当我们想要将A--C--E--F <-- master \ B--D----G--H--I <-- develop 中的更改合并到G-H-I时,我们需要再次合并。如果我们有正常合并,那么master指向F以及D - Git会知道如何执行此操作。但是,如果没有真正的合并,Git将再次回到共同提交E,并比较A - vs - A,然后F -vs - A,试图将两个变化结合起来。

但我们已经进行了I更改!他们在B-D。如果我们有一个真正的合并,Git会知道这一点,而不必解决这个问题。

如果我们很幸运 - 我们常常是这样--Git无论如何都会想到这一点。但是,提交链越长,随着越来越多的变化,Git会越来越不可能自己解决这个问题。

因此,对于squash合并的通常规则是,如果我们要放弃合并的分支,我们应该将它们设为 ,例如,删除名称完全F。删除名称后,无法访问的提交最终会被垃圾收集。

该怎么做,可能

当你使用真正的合并时,缺点是你看到了一切,因为Git同时跟随父母的方式。也就是说,如果你运行:

develop

您会看到所有git log master ABCDE(按日期混合和排序) ,通常,虽然你可以控制这个)。但是,避免这种情况有一个非常重要的特征。你可以运行:

F

相反,要查看提交而不遵循任何&#34;额外&#34;来自合并的父母。此git log --first-parent master 视图会向您显示提交--first-parent,并跳过A-C-E-F。这是因为B-D限制了图遍历以跳过每个合并提交的第二个父项。

答案 1 :(得分:2)

请注意,在更新{{1}之后,正确的工作流程(因为您尚未推送develop)在develop之上 rebase master }}

master

现在您可以推送,因为git checkout master git pull git checkout develop git rebase master 内容基于最新develop内容(并经过验证)。
您可以首先在本地解决任何冲突(从master之上重播develop),然后再推送。

答案 2 :(得分:0)

您可以在master上创建新的单个提交,其中包含develop使用get merge --squash引入的所有更改,然后提交结果。

merge --sqauash会将develop中的所有更改应用到索引,并准备一条提交消息,其中包含来自develop的所有连接提交消息,但它会创建提交。然后由git commit决定编辑准备好的提交消息。