如何在 Dev 处于 head 状态时从 Master 添加一个 "behind" commit 到 Dev?

How to add one "behind" commit from Master to Dev where Dev is in head state?

我实际上正在学习 Git,我想知道实现 "Desired state" 的最佳实践是什么:

Actual state:

Master A-----B------C
             \ 
              \
           Dev D------E------F


Desired state:
Master
A-----B------C
             |
             |
Dev          C------D------E------F

仅供参考:我在 Master/Commit-B 上生产,在 Dev/Commit-D-E-F 上开发。我已经看到我想对生产进行更改,但 Dev/Commit-D-E-F 还没有准备好合并到生产中。

我读过有用的 Whosebug post:How to merge a specific commit in Git 使用 git cherry-pick 但我不太确定我是否应该关注 "This changing of commit IDs breaks git's merging functionality among other things"?我是否需要更改我的提交愿景以不重现这种情况?

当我的开发完成后,我计划将 Dev 中的修改合并到 Master 中:

Future desired state:
Master
A-----B------C------D------E------F

第一个看起来像你在找 git rebase。 在 Dev 时:git rebase master.

Master A-----B------C
             \ 
              \
           Dev D------E------F

After Rebase

Master A-----B------C
                     \ 
                      \
                      Dev D------E------F

但是,我没有得到你的第二张图表。开发D、E和F然后只将F应用到master上是什么想法?然而,对于那种情况,正如您已经提到的,git cherry-pick 似乎是合适的 (git cherry-pick F)。通常,git cherry-pick 不会造成任何问题(至少在我使用它的 "standard" 案例中)。

如评论中所述,了解提交和分支之间的松散关系很重要。一个提交要么可以从一个分支到达,要么不能;它可能可以从许多分支机构到达。它是在签出特定分支时创建的,这一事实并没有赋予它与该分支的任何特殊关系。

您所指出的是您希望在 C 中所做的更改也应用于 dev 分支。有几种方法可以做到这一点。首先让我们 re-draw 你的 "current state" 用一种更好地反映 git 概念的符号

A-----B------C <--(master)
       \ 
        \
         D------E------F <--(dev)

这里我们看到 master 是一个当前指向 C 的引用。 AB 可能最初是在 master 上创建的,并且肯定可以从 master 访问(通过提交 parent 指针);但他们与master的关系也不过如此。事实上,它们也可以从 dev 到达,并且与 dev 的关系与它们在当前状态下与 master 的关系完全相同;这是查看为什么 dev 反映了您在创建这些提交时所做的事情的一种方式。

总之,dev是指向F的ref,从中ABDE也可达。

现在您的 "desired state" 的绘制方式还有一些其他问题;我将针对您可以做的事情拟定几个选项,希望这些概念会更加清晰。

因此,一种选择是变基 dev,以便它包含 C。从技术上讲,这意味着您创建了新的提交(D'E'F'),这些提交是 "replaying" 来自 D、[=42] 的更改的结果=], FC.

git rebase master dev

会给你

A-----B------C <--(master)
              \ 
               \
                D'------E'------F' <--(dev)

请注意,使用此选项,您不需要 C',因为您只是从 dev 生成 C "reachable"。但是,不可能更改 D 的 parent,因此您创建 D'(然后这反过来又迫使您创建 E' 代替 E, 等等)

值得注意的是,D'E' 是代码的未测试状态。 (F' 也是如此,但无论如何您可能都会对其进行测试。)您可能会或可能不会回头测试这些生成的中间快照,但如果您不这样做,可能会干扰未来追踪错误(例如使用 bisect)。如果 D'E' 不够重要,无法测试,那么它们可能不够重要,无法保留;所以在那种情况下你应该考虑交互地做变基并且squashing提交EF,给

A-----B------C <--(master)
              \ 
               \
                DEF <--(dev)

(其中 DEF 是重播来自 DEF 的更改的单个提交)。

当您想将dev合并到master中时,假设master之间没有进一步的变化,您可以选择"fast forward" master ;事实上,如果你说

,那是默认设置
git checkout master
git merge dev

从这种情况。这会给你

A-----B------C------D'------E'------F' <--(dev)(master)

(或

A-----B------C------DEF <--(dev)(master)

如果您使用交互式变基来压缩 dev 提交)。这是许多人喜欢的结果,因为它具有简单的线性度。另一种选择是

git checkout master
git merge --no-ff dev

这给了你

A-----B------C --------------------M <--(master)
              \                   /
               \                 /
                D'------E'------F' <--(dev)

其中 M 将具有与 F' 相同的 TREE(内容状态)(因为 master 没有未在 dev). (再次 D'...F' 将被 DEF 替换,如果你在原始变基期间被压扁。)这有点不寻常,因为 rebase 通常是用于设置 fast-forward 以便您拥有线性历史记录。如果你想保留分支拓扑(这就是你使用 --no-ff 的原因),你可能会选择一种不同的方式来首先反映 Cdev 中的变化。

所以回到你当前的状态,而不是重新设置你的基础 可以master 合并到 dev

git checkout dev
git merge master

这会给你

A-----B--------------------C <--(master)
       \                    \
        \                    \
         D------E------F------M <--(dev)

同样,这使得 C 更改可以在 dev 中访问,而无需重复提交。事实上,您不会以这种方式复制或替换 any 现有提交,如果现有提交已与其他开发人员共享(例如,推送到原点),这尤其好。

但是在这里你创建了一个合并提交(M),有些人认为这些 "backwards merge" 提交是丑陋的混乱。这是一种可以理解的批评;假设你这样做了,然后测试合并结果,然后立即合并回 master;您要么允许 fast-forward 并获得 get

A-----B--------------------C
       \                    \
        \                    \
         D------E------F------M <--(dev)(master)

看起来不错,但是 M 的 parent 没有列在 usually-expected 顺序中,这可能会使使用 --first-parent 浏览历史记录的人感到困惑。 .

或禁止fast-forward并获得

A-----B--------------------C-----M2 <--(master)
       \                    \   /
        \                    \ /
         D------E------F------M <--(dev)

这看起来有点奇怪。

避免这些结果的一个工作流程是考虑 "backwards merge" 临时(仅用于测试)并在测试完成后撤消它。

git checkout dev
git reset --hard HEAD^

这又意味着你最终要存储 probably-untested 合作的状态e.此外,如果 M 有任何冲突,那么您将失去解决这些冲突的方法;如果 dev 没有立即准备好 re-integration 进入 master,您可能会重复 re-doing 该决议。 git rerere 可缓​​解此问题。

fwiw,我的偏好是只接受合并提交 "clutter",因为它通常不会造成任何伤害(而且 git 可以帮助您从日志输出中过滤掉它)。

无论如何,您会看到我们仍然不必创建 C'(因为 C 不会 "belong to master" 以任何方式阻止我们将其直接合并到 dev).将 C 中的更改重播到新的 C' 提交 "on dev" 一个选项,但我建议不要这样做,因为重复提交只会产生风险不必要的合并冲突。如果你真的想这样做,你可以使用 cherry-picking 或者,在你站点的特定情况下,master 的 so-called "squash merge" 到 dev . (还有其他方法。)