Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

1. 基础

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

2. 合并

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

这两个命令都旨在将更改从⼀个分⽀合并到另⼀个分⽀。 但⼆者的合并⽅式却有很⼤的不同。 2.1 概述 Merge 与 Rebase

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 这会在 feature 分⽀中创建⼀个新的 merge commit,它将两个分⽀的历史联系在⼀ 起。 思考:这⾥没有指定 --no-ff 参数,为什么 不是默认的 Fast-Forward Merge 呢? 2.1.1 Merge git checkout feature git merge master

Slide 15

Slide 15 text

https://learngitbranching.js.org/

Slide 16

Slide 16 text

https://learngitbranching.js.org/

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 这会将整个 feature 分⽀移动到 master 分⽀的顶端,从⽽有效地整合了所有 master 分 ⽀上的提交。 但是,与 merge 提交⽅式不同,rebase 通过为原始分⽀中的每个提交创建全新的 commits 来 重写 项⽬历史记录。 2.1.2 Rebase git checkout feature git rebase master

Slide 20

Slide 20 text

https://learngitbranching.js.org/

Slide 21

Slide 21 text

https://learngitbranching.js.org/

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

2.1.3 简单对⽐ Merge vs Rebase

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

2.2 Merge 的三种模式

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

2.2.1 Fast-Forward Merge

Slide 34

Slide 34 text

2.2.1 Fast-Forward Merge

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

特点: • 是 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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

通过⼀个例⼦来说明: 假设有⼀个⽂本⽂件,Alice 和 Bob 同时做了改动,节点如下: 如果只对 Alice 和 Bob 各⾃的⽂件进⾏对⽐,也就是所谓的“diff”(或者也有⼈称之为“Two-Way Merge”),那么⼯具在帮我们做合并时,只知道两个⽂件在同⼀⾏上有差异,却没办法知道在合 并后的版本⾥,到底该保留谁的版本,所以只能交给⽤户⾃⼰⼿⼯来决定。 这就是普通的 Merge ⼯具所能做的。

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

和普通的 Merge ⼯具相⽐,Git 最⼤的不同在于它记录了⽂件的提交历史,因此可以向前回溯⽂件 修改前的“原件”。它在合并时不仅会看两⼈各⾃的⽂件内容,还会看之前的原件。 通过和原来版本的对⽐,就可以清楚地看出, • base/Alice 的对⽐,可以知道 Alice 的改动是删除了 3; • base/Bob 的对⽐,可以知道 Bob 的改动是增加了 1; 这⼀过程可以由⼯具⾃动完成,⽽不⽤像 Two-Way Merge 那样,需要交给⽤户⼿⼯来决定,这就 是 Three-Way Merge。

Slide 46

Slide 46 text

这⾥边的 • 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

Slide 47

Slide 47 text

所谓 Squash Merge,是指 Git 在做两个分⽀间的合并时,会把被合并分⽀上的所 有变更“压缩(squash)”成⼀个提交,追加到当前分⽀的后⾯,作为“合并提交” (merge commit)。 从参与合并的⽂件变更上来说,Squash Merge 和普通 Merge 并没有任何区别,效 果完全⼀样。 唯⼀的区别体现在提交历史上:对于普通的 Merge ⽽⾔,在当前分⽀上的合并提 交通常会有两个 parent;⽽ Squash Merge 却只有⼀个。 2.2.3 Squash Merge

Slide 48

Slide 48 text

⽐如⽬前有两个分⽀,master 和 feature

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

⽐如⽬前有两个分⽀,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

Slide 51

Slide 51 text

⽐如⽬前有两个分⽀,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

Slide 52

Slide 52 text

⽐如⽬前有两个分⽀,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 分⽀删掉,整个 提交历史就会变得⾮常⼲净。

Slide 53

Slide 53 text

⽐如⽬前有两个分⽀,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 分⽀删掉,整个 提交历史就会变得⾮常⼲净。

Slide 54

Slide 54 text

如果在 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 分别何时使⽤?

Slide 55

Slide 55 text

交互式 rebase 使你有机会在将 commits 移动到新分⽀时更改这些 commits。这 ⽐⾃动 rebase 更强⼤,因为它提供了对分⽀提交历史的完全控制。通常,这⽤于 在合并 feature 分⽀到 master 之前清理其杂乱的历史记录。 2.3 交互式 Rebase

Slide 56

Slide 56 text

要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项:

Slide 57

Slide 57 text

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 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。

Slide 58

Slide 58 text

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 命令将它们“浓缩”为⼀个提交:

Slide 59

Slide 59 text

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 命令将它们“浓缩”为⼀个提交:

Slide 60

Slide 60 text

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 命令将它们“浓缩”为⼀个提交:

Slide 61

Slide 61 text

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 根本⽆法做到的事情。

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 2.4 Rebase 的⻩⾦法则

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

永远不要 在 公共分⽀ 上使⽤ Rebase 2.5 Rebase 的⼏种场景

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

2.6 总结 如果你更喜欢⼲净的合并提交,完美的线性历史记录,你就需要开始了解并使⽤ git rebase。 如果你想保留项⽬的完整历史记录并避免重写公共提交的⻛险,你可以坚持下去 git merge。 这两种选择都是完全有效的。

Slide 77

Slide 77 text

3. 回滚

Slide 78

Slide 78 text

3.1 概述 Reset 与 Revert 这两个命令都能实现代码库的回滚操作,但两者的实现⽅式是有本质区别的。 • Reset 是直接删除指定的 commit • Revert 是⽤⼀次新的 commit 来对之前的 commit 做⼀次“逆”操作

Slide 79

Slide 79 text

3.1.1 Reset 例如 hotfix 分⽀上最近两次提交都不要了

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

https://learngitbranching.js.org/

Slide 82

Slide 82 text

https://learngitbranching.js.org/

Slide 83

Slide 83 text

Git Reset 的三种模式 --soft、--mixed、--hard •--soft:仅重置提交区(commit) •--mixed:重置提交区(commit)和暂存区 (staged) •--hard:重置提交区(commit)和暂存区 (staged),并使⽤ commit 的内容填充⼯ 作区(working directory)

Slide 84

Slide 84 text

3.1.2 Revert

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

3.1.2 Revert git checkout feature git revert --no-commit HEAD~2 假如 V1 的修改不需要了

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

对于由 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 我们保留那个分⽀)

Slide 94

Slide 94 text

对于由 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 我们保留那个分⽀)

Slide 95

Slide 95 text

3.2 总结 在运⾏ git reset 改写历史之前,和使⽤ git rebase ⼀样,总是问⾃⼰: 还有其他⼈在⽤这个分⽀吗? 如果答案是肯定的,那不要敲下回⻋键,⽽是优先考虑采⽤ git revert。 注:git reset 和 git rebase 在改写历史的时候均需要配合 force push 使⽤,所以公共分⽀均应开启分⽀保护以规 避被改写历史的问题。

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

练练⼿ 有这么⼀段提交记录,现在想只保留 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

Slide 100

Slide 100 text

4. 其他

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免 MR 在准备就绪前被合并 使⽤: • 在 MR 的标题开头添加 WIP: • 当已经准备好被合并,编辑⼯单来⼿动删除 WIP: 或者使⽤“Resolve WIP status”按钮

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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,⾮常难看。

Slide 111

Slide 111 text

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,⾮常难看。

Slide 112

Slide 112 text

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 的⼀个典型场景:

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

Q&A

Slide 116

Slide 116 text

Thanks