Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Merge and rollback gracefully in Git

98f49890b900ee928c8eaa95d3d4aa43?s=47 Yibo
January 16, 2021

Merge and rollback gracefully in Git

98f49890b900ee928c8eaa95d3d4aa43?s=128

Yibo

January 16, 2021
Tweet

Transcript

  1. 2021.1 赵⼀博 Git 的那些事⼉ 优雅的合并与回滚

  2. None
  3. 1. 基础

  4. 1.1 三个区域 ⼯作区、暂存区和提交区 • ⼯作区(Working Directory):没有修改、修 改后未使⽤ git add 的⽂件

    • 暂存区(Staged/Index):新增/修改后的⽂件 调⽤ git add 后都会被添加这⾥ • 提交区(Commit/Local Repository):所有添 加到暂存区⾥的⽂件通过 git commit 之后会被 统⼀添加这⾥
  5. 1.1 三个区域 ⼯作区、暂存区和提交区 • ⼯作区(Working Directory):没有修改、修 改后未使⽤ git add 的⽂件

    • 暂存区(Staged/Index):新增/修改后的⽂件 调⽤ git add 后都会被添加这⾥ • 提交区(Commit/Local Repository):所有添 加到暂存区⾥的⽂件通过 git commit 之后会被 统⼀添加这⾥ • /refs/remotes:远程仓库的引⽤(不可修改) • remote:远程仓库
  6. 2. 合并

  7. 当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么?

  8. 当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么?

  9. 当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么? 假设在 master 分⽀上的新提交与你正在开发的

    feature 相关,你会怎么办?
  10. 这两个命令都旨在将更改从⼀个分⽀合并到另⼀个分⽀。 但⼆者的合并⽅式却有很⼤的不同。 2.1 概述 Merge 与 Rebase

  11. 最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 2.1.1 Merge

  12. 最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 2.1.1 Merge git checkout feature

    git merge master
  13. 最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 这会在 feature 分⽀中创建⼀个新的 merge commit,它将两个分⽀的历史联系在⼀

    起。 2.1.1 Merge git checkout feature git merge master
  14. 最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 这会在 feature 分⽀中创建⼀个新的 merge commit,它将两个分⽀的历史联系在⼀

    起。 思考:这⾥没有指定 --no-ff 参数,为什么 不是默认的 Fast-Forward Merge 呢? 2.1.1 Merge git checkout feature git merge master
  15. https://learngitbranching.js.org/

  16. https://learngitbranching.js.org/

  17. 可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 2.1.2 Rebase

  18. 可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 2.1.2 Rebase git checkout feature git

    rebase master
  19. 可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 这会将整个 feature 分⽀移动到 master 分⽀的顶端,从⽽有效地整合了所有 master

    分 ⽀上的提交。 但是,与 merge 提交⽅式不同,rebase 通过为原始分⽀中的每个提交创建全新的 commits 来 重写 项⽬历史记录。 2.1.2 Rebase git checkout feature git rebase master
  20. https://learngitbranching.js.org/

  21. https://learngitbranching.js.org/

  22. ⾸先,它消除了 git merge 所需的不必要的合并提交; 其次,正如在上图中所看到的,rebase 会产⽣完美线性的项⽬历史记录,可以在 feature分⽀上没有任何分叉的情况下⼀直追寻到项⽬的初始提交。 可以看出,Rebase 的主要好处是可以获得更清晰的项⽬历史。

  23. 2.1.3 简单对⽐ Merge vs Rebase

  24. 2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •

    源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史
  25. 2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •

    源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。
  26. 2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •

    源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史
  27. 2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •

    源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史 • 弊端 • 可能会丢失合并提交的上下⽂(也就⽆法看到上游 更改是何时合并到 feature 中的) • 如果不正确地重写了历史,可能会导致严重的问题
  28. 2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •

    源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史 • 弊端 • 可能会丢失合并提交的上下⽂(也就⽆法看到上游 更改是何时合并到 feature 中的) • 如果不正确地重写了历史,可能会导致严重的问题
  29. 2.2 Merge 的三种模式

  30. • Fast-Forward Merge 2.2 Merge 的三种模式

  31. • Fast-Forward Merge • Three-Way Merge 2.2 Merge 的三种模式

  32. • Fast-Forward Merge • Three-Way Merge • Squash Merge 2.2

    Merge 的三种模式
  33. 2.2.1 Fast-Forward Merge

  34. 2.2.1 Fast-Forward Merge

  35. 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把

    master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
  36. 特点: 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git

    只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
  37. 特点: • 是 git merge ⽆参数时的默认合并⽅式 由于 master 分⽀从 C2

    开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
  38. 特点: • 是 git merge ⽆参数时的默认合并⽅式 • 如果删除被合并分⽀(通常被称为 topic 分⽀),会丢掉

    topic 分⽀信息 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
  39. 特点: • 是 git merge ⽆参数时的默认合并⽅式 • 如果删除被合并分⽀(通常被称为 topic 分⽀),会丢掉

    topic 分⽀信息 缺点:作为被合并的 topic 分⽀,它的提交历史在合并以后会和 master 分⽀的提交历史重合 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
  40. 如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 2.2.2 Three-Way Merge

  41. 如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 2.2.2 Three-Way Merge

  42. 如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 看起来这只有“两⽅”,这所谓的 “三⽅”到底是哪三⽅呢? 2.2.2

    Three-Way Merge
  43. 通过⼀个例⼦来说明: 假设有⼀个⽂本⽂件,Alice 和 Bob 同时做了改动,节点如下: 如果只对 Alice 和 Bob 各⾃的⽂件进⾏对⽐,也就是所谓的“diff”(或者也有⼈称之为“Two-Way

    Merge”),那么⼯具在帮我们做合并时,只知道两个⽂件在同⼀⾏上有差异,却没办法知道在合 并后的版本⾥,到底该保留谁的版本,所以只能交给⽤户⾃⼰⼿⼯来决定。 这就是普通的 Merge ⼯具所能做的。
  44. 和普通的 Merge ⼯具相⽐,Git 最⼤的不同在于它记录了⽂件的提交历史,因此可以向前回溯⽂件 修改前的“原件”。它在合并时不仅会看两⼈各⾃的⽂件内容,还会看之前的原件。 通过和原来版本的对⽐,就可以清楚地看出, • base/Alice 的对⽐,可以知道 Alice

    的改动是删除了 3; • base/Bob 的对⽐,可以知道 Bob 的改动是增加了 1;
  45. 和普通的 Merge ⼯具相⽐,Git 最⼤的不同在于它记录了⽂件的提交历史,因此可以向前回溯⽂件 修改前的“原件”。它在合并时不仅会看两⼈各⾃的⽂件内容,还会看之前的原件。 通过和原来版本的对⽐,就可以清楚地看出, • base/Alice 的对⽐,可以知道 Alice

    的改动是删除了 3; • base/Bob 的对⽐,可以知道 Bob 的改动是增加了 1; 这⼀过程可以由⼯具⾃动完成,⽽不⽤像 Two-Way Merge 那样,需要交给⽤户⼿⼯来决定,这就 是 Three-Way Merge。
  46. 这⾥边的 • 1 是公共祖先 • 2 是 HEAD,git ⽂档中有时候也称作 ours

    • 3 是 MERGE_HEAD,git ⽂档中有时候也称作 theirs 在 git 的官⽅⽂档⾥是有提到 Three-Way Merge 的 参考: • git 官⽅关于 Three-Way Merge 的介绍:http://git-scm.com/docs/git-merge#_true_merge • Wikipedia 中关于 Three-Way Merge 的介绍:https://en.wikipedia.org/wiki/Merge_(version_control) • 这篇论⽂介绍了 diff3 的算法:https://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf
  47. 所谓 Squash Merge,是指 Git 在做两个分⽀间的合并时,会把被合并分⽀上的所 有变更“压缩(squash)”成⼀个提交,追加到当前分⽀的后⾯,作为“合并提交” (merge commit)。 从参与合并的⽂件变更上来说,Squash Merge

    和普通 Merge 并没有任何区别,效 果完全⼀样。 唯⼀的区别体现在提交历史上:对于普通的 Merge ⽽⾔,在当前分⽀上的合并提 交通常会有两个 parent;⽽ Squash Merge 却只有⼀个。 2.2.3 Squash Merge
  48. ⽐如⽬前有两个分⽀,master 和 feature

  49. ⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |

    * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 git log --oneline --graph --all
  50. ⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |

    * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 现在执⾏⼀次 Squash Merge git log --oneline --graph --all git merge --squash feature
  51. ⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |

    * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 现在执⾏⼀次 Squash Merge 然后⽣成新的提交 git log --oneline --graph --all git merge --squash feature git commit -m c6
  52. ⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |

    * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 * 0b7fd35 (HEAD -> master) c6 * 098be39 c5 | * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 现在执⾏⼀次 Squash Merge 然后⽣成新的提交 此时再看提交记录 git log --oneline --graph --all git merge --squash feature git commit -m c6 git log --oneline --graph --all 作为合并提交的 c6,只有⼀个 parent,即:c5。 ⽽且,如果这个时候我们把 feature 分⽀删掉,整个 提交历史就会变得⾮常⼲净。
  53. ⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |

    * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 * 0b7fd35 (HEAD -> master) c6 * 098be39 c5 | * 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 现在执⾏⼀次 Squash Merge 然后⽣成新的提交 此时再看提交记录 git log --oneline --graph --all git merge --squash feature git commit -m c6 git log --oneline --graph --all 作为合并提交的 c6,只有⼀个 parent,即:c5。 ⽽且,如果这个时候我们把 feature 分⽀删掉,整个 提交历史就会变得⾮常⼲净。
  54. 如果在 topic 分⽀与 merge 分⽀⽆“分叉”的时候,可以使⽤ Fast-Forward Merge(默认,--ff)。(仅仅是可以,不建议) 如果在 topic 分⽀上,完整的提交历史⾥包含了很多中间提交(intermediate

    commit),⽐如:改正⼀个⼩⼩的拼写错误可能也会成为⼀个独⽴的提交,⽽我 们并不希望在合并时把这些细节都反映在 merge 分⽀的提交历史⾥,这时就选择 Squash Merge(--squash)。 如果在 topic 分⽀上,有多次不同类型的提交,⽐如:有和功能相关的 feat 的提 交,也有⽂档相关的 docs 的提交,我们需要把这些提交都反映在 merge 分⽀的 提交历史⾥,这时我们就选择 Three-Way Merge(--no-ff)。 2.2.1 三种 Merge 分别何时使⽤?
  55. 交互式 rebase 使你有机会在将 commits 移动到新分⽀时更改这些 commits。这 ⽐⾃动 rebase 更强⼤,因为它提供了对分⽀提交历史的完全控制。通常,这⽤于 在合并

    feature 分⽀到 master 之前清理其杂乱的历史记录。 2.3 交互式 Rebase
  56. 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项:

  57. git checkout feature git rebase -i master 这将打开⼀个⽂本编辑器,列出即将移动的所有提交: pick 33d5b7a

    Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项: 通过更改 pick 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。
  58. git checkout feature git rebase -i master 这将打开⼀个⽂本编辑器,列出即将移动的所有提交: pick 33d5b7a

    Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项: 通过更改 pick 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。 例如,如果第⼆次提交 fix 了第⼀次提交中的⼀个⼩问 题,可以使⽤以下 fixup 命令将它们“浓缩”为⼀个提交:
  59. git checkout feature git rebase -i master 这将打开⼀个⽂本编辑器,列出即将移动的所有提交: pick 33d5b7a

    Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项: 通过更改 pick 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。 pick 33d5b7a Message for commit #1 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 fixup 例如,如果第⼆次提交 fix 了第⼀次提交中的⼀个⼩问 题,可以使⽤以下 fixup 命令将它们“浓缩”为⼀个提交:
  60. git checkout feature git rebase -i master 这将打开⼀个⽂本编辑器,列出即将移动的所有提交: pick 33d5b7a

    Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项: 通过更改 pick 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。 pick 33d5b7a Message for commit #1 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 保存并关闭⽂件时,Git 将根据指示执⾏ rebase, 从⽽产⽣如下所示的项⽬历史记录 fixup 例如,如果第⼆次提交 fix 了第⼀次提交中的⼀个⼩问 题,可以使⽤以下 fixup 命令将它们“浓缩”为⼀个提交:
  61. git checkout feature git rebase -i master 这将打开⼀个⽂本编辑器,列出即将移动的所有提交: pick 33d5b7a

    Message for commit #1 pick 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项: 通过更改 pick 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。 pick 33d5b7a Message for commit #1 9480b3d Message for commit #2 pick 5c67e61 Message for commit #3 保存并关闭⽂件时,Git 将根据指示执⾏ rebase, 从⽽产⽣如下所示的项⽬历史记录 fixup 例如,如果第⼆次提交 fix 了第⼀次提交中的⼀个⼩问 题,可以使⽤以下 fixup 命令将它们“浓缩”为⼀个提交: 消除这种⽆意义的提交使你的功能历史更容易理 解。这是 git merge 根本⽆法做到的事情。
  62. 操作 说明 pick 采⽤该提交(默认⾏为) reword 采⽤该提交,但要求修改 commit message edit 采⽤该提交,但要求修改提交记录的信息,如:作者名称,邮箱地址等

    squash 采⽤该提交,但它会被并⼊前⼀条提交 fixup 类似 squash,但是会丢弃这条提交记录的⽇志信息 exec 执⾏指定的 shell 脚本或命令 drop 丢弃该提交 除了 fixup 外,rebase 还有以下可供选择的操作
  63. 操作 说明 pick 采⽤该提交(默认⾏为) reword 采⽤该提交,但要求修改 commit message edit 采⽤该提交,但要求修改提交记录的信息,如:作者名称,邮箱地址等

    squash 采⽤该提交,但它会被并⼊前⼀条提交 fixup 类似 squash,但是会丢弃这条提交记录的⽇志信息 exec 执⾏指定的 shell 脚本或命令 drop 丢弃该提交 除了 fixup 外,rebase 还有以下可供选择的操作 ⼀旦我们理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。
  64. ⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 2.4 Rebase 的⻩⾦法则

  65. ⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 1. 永远不要 在 公共分⽀ 上使⽤它

    2.4 Rebase 的⻩⾦法则
  66. ⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 1. 永远不要 在 公共分⽀ 上使⽤它

    2.4 Rebase 的⻩⾦法则 2. 永远不要 在 公共分⽀ 上使⽤它 3. 永远不要 在 公共分⽀ 上使⽤它
  67. 试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么

  68. 试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么

  69. 试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master

    分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。
  70. 试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master

    分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。 此时,同步两个 master 分⽀的唯⼀⽅法是将它们 merge 在⼀ 起,但是这样会产⽣额外的合并提交和两组包含相同更改的提交 (原始提交和通过 rebase 更改的提交)。 这是⼀个令⼈⾮常困惑的情况。
  71. 试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master

    分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。 此时,同步两个 master 分⽀的唯⼀⽅法是将它们 merge 在⼀ 起,但是这样会产⽣额外的合并提交和两组包含相同更改的提交 (原始提交和通过 rebase 更改的提交)。 这是⼀个令⼈⾮常困惑的情况。 因此,在运⾏ git rebase 命令之前,总是问⾃⼰,还有其他⼈在⽤这个分⽀吗? 如果答案是肯定的,那就把你的⼿从键盘上移开,开始考虑采⽤⾮破坏性的⽅式进 ⾏改变(例如 git merge)。
  72. 永远不要 在 公共分⽀ 上使⽤ Rebase 2.5 Rebase 的⼏种场景

  73. 永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) git

    checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
  74. 永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) •

    将上游更改合并到功能分⽀中 git checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
  75. 永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) •

    将上游更改合并到功能分⽀中 • 在提交 PR 之前清理代码 git checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
  76. 2.6 总结 如果你更喜欢⼲净的合并提交,完美的线性历史记录,你就需要开始了解并使⽤ git rebase。 如果你想保留项⽬的完整历史记录并避免重写公共提交的⻛险,你可以坚持下去 git merge。 这两种选择都是完全有效的。

  77. 3. 回滚

  78. 3.1 概述 Reset 与 Revert 这两个命令都能实现代码库的回滚操作,但两者的实现⽅式是有本质区别的。 • Reset 是直接删除指定的 commit

    • Revert 是⽤⼀次新的 commit 来对之前的 commit 做⼀次“逆”操作
  79. 3.1.1 Reset 例如 hotfix 分⽀上最近两次提交都不要了

  80. 3.1.1 Reset 例如 hotfix 分⽀上最近两次提交都不要了 git checkout hotfix git reset

    HEAD~2
  81. https://learngitbranching.js.org/

  82. https://learngitbranching.js.org/

  83. Git Reset 的三种模式 --soft、--mixed、--hard •--soft:仅重置提交区(commit) •--mixed:重置提交区(commit)和暂存区 (staged) •--hard:重置提交区(commit)和暂存区 (staged),并使⽤ commit

    的内容填充⼯ 作区(working directory)
  84. 3.1.2 Revert

  85. 3.1.2 Revert 假如 V1 的修改不需要了

  86. 3.1.2 Revert git checkout feature git revert --no-commit HEAD~2 假如

    V1 的修改不需要了
  87. 假如 V2、V3 的修改不需要了

  88. git checkout feature git revert --no-commit HEAD~2..HEAD 假如 V2、V3 的修改不需要了

  89. git checkout feature git revert --no-commit HEAD~2..HEAD 假如 V2、V3 的修改不需要了

    m..n 表示范围的话,开闭类似 (m, n] ,等价的写法 git revert --no-commit HEAD HEAD~1 git revert --no-commit V3 V2
  90. 对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m

    参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码
  91. 对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m

    参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码
  92. 对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m

    参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码 git revert -m 1 M1 对 M1 进⾏ revert (此时 M1 有两个 parent,所以我们需要使⽤ -m 或 --mainline 告诉 git 我们保留那个分⽀)
  93. 对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m

    参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码 git revert -m 1 M1 如果回滚后,⼜要上该功能(或者发现回滚错了) 此时再 git merge feature 你会发现 feature 分⽀代码并没有回来 对 M1 进⾏ revert (此时 M1 有两个 parent,所以我们需要使⽤ -m 或 --mainline 告诉 git 我们保留那个分⽀)
  94. 对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m

    参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码 git revert -m 1 M1 git revert -m 1 M1’ 如果回滚后,⼜要上该功能(或者发现回滚错了) 此时再 git merge feature 你会发现 feature 分⽀代码并没有回来 正确的操作是对 M1’ 再次 revert(负负得正) 对 M1 进⾏ revert (此时 M1 有两个 parent,所以我们需要使⽤ -m 或 --mainline 告诉 git 我们保留那个分⽀)
  95. 3.2 总结 在运⾏ git reset 改写历史之前,和使⽤ git rebase ⼀样,总是问⾃⼰: 还有其他⼈在⽤这个分⽀吗?

    如果答案是肯定的,那不要敲下回⻋键,⽽是优先考虑采⽤ git revert。 注:git reset 和 git rebase 在改写历史的时候均需要配合 force push 使⽤,所以公共分⽀均应开启分⽀保护以规 避被改写历史的问题。
  96. 练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? text.txt commits

  97. 练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? Revert :注意 merge commit,另外需解冲突 text.txt commits

  98. 练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? Revert :注意 merge commit,另外需解冲突 Reset:也可以不改写历史 text.txt

    commits
  99. 练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? git reset --hard cfbe7f6 #2 git

    reset --soft 7ca8127 #7 git commit -m "Reverted 3 4 5 6 7" Revert :注意 merge commit,另外需解冲突 Reset:也可以不改写历史 text.txt commits
  100. 4. 其他

  101. 4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免

    MR 在准备就绪前被合并
  102. 4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免

    MR 在准备就绪前被合并 使⽤:
  103. 4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免

    MR 在准备就绪前被合并 使⽤: • 在 MR 的标题开头添加 WIP:
  104. 4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免

    MR 在准备就绪前被合并 使⽤: • 在 MR 的标题开头添加 WIP: • 当已经准备好被合并,编辑⼯单来⼿动删除 WIP: 或者使⽤“Resolve WIP status”按钮
  105. 4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做?

  106. 4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交

  107. 4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤

    IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout)
  108. 4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤

    IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout) 3. 可以使⽤ git stash 命令暂存未提交的修改
  109. 4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤

    IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout) 3. 可以使⽤ git stash 命令暂存未提交的修改 git stash 的具体命令可以⾃⾏搜索,也可以直接使⽤图形化的 GUI 简化命令操作。
  110. 4.3 git pull --rebase git pull = git pull --merge

    = git fetch + git merge FETCH_HEAD git pull --rebase = git fetch + git rebase FETCH_HEAD ⼤家都基于 master 拉出分⽀进⾏并⾏开发,这⾥的分⽀可能是多到数⼗个。然后彼此在进⾏⾃⼰的逻 辑编写,时间可能需要⼏天或者⼏周。在这期间你可能需要时不时的需要 pull 下远程 master 分⽀上的 同事的提交。这是个好的习惯,这样下去就可以避免你在⼀个⽆⽤的代码上进⾏⻓期的开发,回头来看 这些代码不是新的代码。 如果使⽤默认的 git pull(默认是 merge 模式),那么往往在 pull 的时候会多⼀条 merge commit,是 在 git pull --merge 的时候⾃动⽣成的。如果多⼈多次如此操作,那么提交记录就会出现很多条这种⾃ 从⽣成的 merge commit,⾮常难看。
  111. 4.3 git pull --rebase --rebase 的本意是想让事情的发展看起来很连续和优美,⽽不是多出很多⽆⽤的 merge commit 。 git

    pull = git pull --merge = git fetch + git merge FETCH_HEAD git pull --rebase = git fetch + git rebase FETCH_HEAD ⼤家都基于 master 拉出分⽀进⾏并⾏开发,这⾥的分⽀可能是多到数⼗个。然后彼此在进⾏⾃⼰的逻 辑编写,时间可能需要⼏天或者⼏周。在这期间你可能需要时不时的需要 pull 下远程 master 分⽀上的 同事的提交。这是个好的习惯,这样下去就可以避免你在⼀个⽆⽤的代码上进⾏⻓期的开发,回头来看 这些代码不是新的代码。 如果使⽤默认的 git pull(默认是 merge 模式),那么往往在 pull 的时候会多⼀条 merge commit,是 在 git pull --merge 的时候⾃动⽣成的。如果多⼈多次如此操作,那么提交记录就会出现很多条这种⾃ 从⽣成的 merge commit,⾮常难看。
  112. Alice 基于 master 创建了⼀个 topic 分⽀ A,Bob 基于 master 创建了⼀个不相⼲的

    topic 分⽀ B Alice:git checkout master && git pull(Master is already up to date) Bob:git checkout master && git pull(Master is already up to date) Alice:git merge topic-branch-A Bob:git merge topic-branch-B Bob:git push origin master(在 Alice 之前) Alice:git push origin master(会被拒绝,因为这不是⼀个 fast-forward merge) Alice:git pull --rebase origin master(这时 Bob 的提交被拉下来,Alice 的提交放到 Bob 的后边) Alice:git push origin master(不会产⽣⽆⽤的 merge commit) 使⽤ git pull --rebase 的⼀个典型场景:
  113. 4.4 Cherry Pick 摘樱桃 使⽤ cherry-pick,可以从其他分⽀复制指定的提交,然后导⼊到现在的分⽀。 主要使⽤的场景: • 把弄错分⽀的提交移动到正确的地⽅ •

    把其他分⽀的提交添加到现在的分⽀ 例如: a - b - c - d Master \ e - f - g Feature
  114. 4.4 Cherry Pick 摘樱桃 使⽤ cherry-pick,可以从其他分⽀复制指定的提交,然后导⼊到现在的分⽀。 主要使⽤的场景: • 把弄错分⽀的提交移动到正确的地⽅ •

    把其他分⽀的提交添加到现在的分⽀ git checkout master git cherry-pick f 例如: a - b - c - d - f Master \ e - f - g Feature
  115. Q&A

  116. Thanks