Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
Merge and rollback gracefully in Git
Yibo
January 16, 2021
Technology
0
13
Merge and rollback gracefully in Git
Yibo
January 16, 2021
Tweet
Share
More Decks by Yibo
See All by Yibo
zhaoyibo
0
12
zhaoyibo
0
9
Other Decks in Technology
See All in Technology
hayatan
0
200
viva_tweet_x
1
430
nisshii0313
1
170
harshbothra
0
140
yshr1200
0
170
koukyo1994
3
530
clustervr
0
250
dena_tech
2
540
kentaro
1
610
miura55
0
360
lancers_pr
4
1.5k
shoichiron
1
150
Featured
See All Featured
roundedbygravity
84
7.9k
mthomps
38
2.3k
chriscoyier
779
240k
jmmastey
10
620
shlominoach
176
7.5k
moore
125
21k
jakevdp
775
200k
jonrohan
1021
380k
robhawkes
52
2.8k
sachag
267
17k
cromwellryan
104
6.2k
zenorocha
296
40k
Transcript
2021.1 赵⼀博 Git 的那些事⼉ 优雅的合并与回滚
None
1. 基础
1.1 三个区域 ⼯作区、暂存区和提交区 • ⼯作区(Working Directory):没有修改、修 改后未使⽤ git add 的⽂件
• 暂存区(Staged/Index):新增/修改后的⽂件 调⽤ git add 后都会被添加这⾥ • 提交区(Commit/Local Repository):所有添 加到暂存区⾥的⽂件通过 git commit 之后会被 统⼀添加这⾥
1.1 三个区域 ⼯作区、暂存区和提交区 • ⼯作区(Working Directory):没有修改、修 改后未使⽤ git add 的⽂件
• 暂存区(Staged/Index):新增/修改后的⽂件 调⽤ git add 后都会被添加这⾥ • 提交区(Commit/Local Repository):所有添 加到暂存区⾥的⽂件通过 git commit 之后会被 统⼀添加这⾥ • /refs/remotes:远程仓库的引⽤(不可修改) • remote:远程仓库
2. 合并
当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么?
当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么?
当你在专⽤分⽀上开发新 feature 时,然后另⼀个团队成员在 master 分⽀提交 了新的 commits,这会发⽣什么? 假设在 master 分⽀上的新提交与你正在开发的
feature 相关,你会怎么办?
这两个命令都旨在将更改从⼀个分⽀合并到另⼀个分⽀。 但⼆者的合并⽅式却有很⼤的不同。 2.1 概述 Merge 与 Rebase
最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 2.1.1 Merge
最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 2.1.1 Merge git checkout feature
git merge master
最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 这会在 feature 分⽀中创建⼀个新的 merge commit,它将两个分⽀的历史联系在⼀
起。 2.1.1 Merge git checkout feature git merge master
最简单的⽅式是通过以下命令将 master 分⽀合并到 feature 分⽀中: 这会在 feature 分⽀中创建⼀个新的 merge commit,它将两个分⽀的历史联系在⼀
起。 思考:这⾥没有指定 --no-ff 参数,为什么 不是默认的 Fast-Forward Merge 呢? 2.1.1 Merge git checkout feature git merge master
https://learngitbranching.js.org/
https://learngitbranching.js.org/
可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 2.1.2 Rebase
可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 2.1.2 Rebase git checkout feature git
rebase master
可以使⽤以下命令将 master 分⽀合并到 feature分⽀上: 这会将整个 feature 分⽀移动到 master 分⽀的顶端,从⽽有效地整合了所有 master
分 ⽀上的提交。 但是,与 merge 提交⽅式不同,rebase 通过为原始分⽀中的每个提交创建全新的 commits 来 重写 项⽬历史记录。 2.1.2 Rebase git checkout feature git rebase master
https://learngitbranching.js.org/
https://learngitbranching.js.org/
⾸先,它消除了 git merge 所需的不必要的合并提交; 其次,正如在上图中所看到的,rebase 会产⽣完美线性的项⽬历史记录,可以在 feature分⽀上没有任何分叉的情况下⼀直追寻到项⽬的初始提交。 可以看出,Rebase 的主要好处是可以获得更清晰的项⽬历史。
2.1.3 简单对⽐ Merge vs Rebase
2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •
源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史
2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •
源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。
2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •
源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史
2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •
源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史 • 弊端 • 可能会丢失合并提交的上下⽂(也就⽆法看到上游 更改是何时合并到 feature 中的) • 如果不正确地重写了历史,可能会导致严重的问题
2.1.3 简单对⽐ Merge vs Rebase • 好处 • 保持源分⽀的原始上下⽂ •
源分⽀上的提交与其他分⽀的提交是分开的 • 可以保留提交历史 • 弊端 • 乱 feature 分⽀每次需要合并上游更改时,它都将产 ⽣⼀个额外的合并提交。如果master 提交⾮常活 跃,这可能会严重污染你的 feature 分⽀历史记 录,它可能使其他开发⼈员难以理解项⽬的历史 记录。 • 好处 • 代码历史是简化的、线性的、可读的 • 与许多独⽴的特性分⽀的提交历史相⽐,操作单个 提交历史更容易 • ⼲净、清晰的提交信息可以更好地跟踪⼀个 bug 或何时引⼊的某个功能,可以避免众多的单⾏提交 污染历史 • 弊端 • 可能会丢失合并提交的上下⽂(也就⽆法看到上游 更改是何时合并到 feature 中的) • 如果不正确地重写了历史,可能会导致严重的问题
2.2 Merge 的三种模式
• Fast-Forward Merge 2.2 Merge 的三种模式
• Fast-Forward Merge • Three-Way Merge 2.2 Merge 的三种模式
• Fast-Forward Merge • Three-Way Merge • Squash Merge 2.2
Merge 的三种模式
2.2.1 Fast-Forward Merge
2.2.1 Fast-Forward Merge
由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把
master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
特点: 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git
只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
特点: • 是 git merge ⽆参数时的默认合并⽅式 由于 master 分⽀从 C2
开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
特点: • 是 git merge ⽆参数时的默认合并⽅式 • 如果删除被合并分⽀(通常被称为 topic 分⽀),会丢掉
topic 分⽀信息 由于 master 分⽀从 C2 开始与 feat 分叉以后就再也没 有新的提交了,所以 Git 只是简单地把 master 的 head 指针向前移动到 C4,合并就完成了。 这就是所谓的 Fast-Forward Merge。Fast-Forward Merge 要求参与合并的两个分⽀上的提交必须是“⼀脉 相承”的⽗⼦或祖孙关系。 因为不涉及内容变更的⽐较,所以这种合并⽅式效率 很⾼。 2.2.1 Fast-Forward Merge
特点: • 是 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
如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 2.2.2 Three-Way Merge
如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 2.2.2 Three-Way Merge
如果在合并时使⽤ --no-ff 参数,Git 就会采⽤ Three-Way Merge(三⽅合并)对两个分⽀进⾏合 并。 看起来这只有“两⽅”,这所谓的 “三⽅”到底是哪三⽅呢? 2.2.2
Three-Way Merge
通过⼀个例⼦来说明: 假设有⼀个⽂本⽂件,Alice 和 Bob 同时做了改动,节点如下: 如果只对 Alice 和 Bob 各⾃的⽂件进⾏对⽐,也就是所谓的“diff”(或者也有⼈称之为“Two-Way
Merge”),那么⼯具在帮我们做合并时,只知道两个⽂件在同⼀⾏上有差异,却没办法知道在合 并后的版本⾥,到底该保留谁的版本,所以只能交给⽤户⾃⼰⼿⼯来决定。 这就是普通的 Merge ⼯具所能做的。
和普通的 Merge ⼯具相⽐,Git 最⼤的不同在于它记录了⽂件的提交历史,因此可以向前回溯⽂件 修改前的“原件”。它在合并时不仅会看两⼈各⾃的⽂件内容,还会看之前的原件。 通过和原来版本的对⽐,就可以清楚地看出, • base/Alice 的对⽐,可以知道 Alice
的改动是删除了 3; • base/Bob 的对⽐,可以知道 Bob 的改动是增加了 1;
和普通的 Merge ⼯具相⽐,Git 最⼤的不同在于它记录了⽂件的提交历史,因此可以向前回溯⽂件 修改前的“原件”。它在合并时不仅会看两⼈各⾃的⽂件内容,还会看之前的原件。 通过和原来版本的对⽐,就可以清楚地看出, • base/Alice 的对⽐,可以知道 Alice
的改动是删除了 3; • base/Bob 的对⽐,可以知道 Bob 的改动是增加了 1; 这⼀过程可以由⼯具⾃动完成,⽽不⽤像 Two-Way Merge 那样,需要交给⽤户⼿⼯来决定,这就 是 Three-Way Merge。
这⾥边的 • 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
所谓 Squash Merge,是指 Git 在做两个分⽀间的合并时,会把被合并分⽀上的所 有变更“压缩(squash)”成⼀个提交,追加到当前分⽀的后⾯,作为“合并提交” (merge commit)。 从参与合并的⽂件变更上来说,Squash Merge
和普通 Merge 并没有任何区别,效 果完全⼀样。 唯⼀的区别体现在提交历史上:对于普通的 Merge ⽽⾔,在当前分⽀上的合并提 交通常会有两个 parent;⽽ Squash Merge 却只有⼀个。 2.2.3 Squash Merge
⽐如⽬前有两个分⽀,master 和 feature
⽐如⽬前有两个分⽀,master 和 feature * 098be39 (HEAD -> master) c5 |
* 0f029c3 (feature) c4 | * c594fa9 c3 |/ * 9229adb c2 * cfbff28 c1 git log --oneline --graph --all
⽐如⽬前有两个分⽀,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
⽐如⽬前有两个分⽀,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
⽐如⽬前有两个分⽀,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 分⽀删掉,整个 提交历史就会变得⾮常⼲净。
⽐如⽬前有两个分⽀,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 分⽀删掉,整个 提交历史就会变得⾮常⼲净。
如果在 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 分别何时使⽤?
交互式 rebase 使你有机会在将 commits 移动到新分⽀时更改这些 commits。这 ⽐⾃动 rebase 更强⼤,因为它提供了对分⽀提交历史的完全控制。通常,这⽤于 在合并
feature 分⽀到 master 之前清理其杂乱的历史记录。 2.3 交互式 Rebase
要使⽤交互式 rebase,需要使⽤ git rebase 和 -i 选项:
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 命令或重新排序条⽬,你可以使分⽀ 的历史记录看起来像你想要的任何内容。
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 命令将它们“浓缩”为⼀个提交:
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 命令将它们“浓缩”为⼀个提交:
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 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 根本⽆法做到的事情。
操作 说明 pick 采⽤该提交(默认⾏为) reword 采⽤该提交,但要求修改 commit message edit 采⽤该提交,但要求修改提交记录的信息,如:作者名称,邮箱地址等
squash 采⽤该提交,但它会被并⼊前⼀条提交 fixup 类似 squash,但是会丢弃这条提交记录的⽇志信息 exec 执⾏指定的 shell 脚本或命令 drop 丢弃该提交 除了 fixup 外,rebase 还有以下可供选择的操作
操作 说明 pick 采⽤该提交(默认⾏为) reword 采⽤该提交,但要求修改 commit message edit 采⽤该提交,但要求修改提交记录的信息,如:作者名称,邮箱地址等
squash 采⽤该提交,但它会被并⼊前⼀条提交 fixup 类似 squash,但是会丢弃这条提交记录的⽇志信息 exec 执⾏指定的 shell 脚本或命令 drop 丢弃该提交 除了 fixup 外,rebase 还有以下可供选择的操作 ⼀旦我们理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。
⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 2.4 Rebase 的⻩⾦法则
⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 1. 永远不要 在 公共分⽀ 上使⽤它
2.4 Rebase 的⻩⾦法则
⼀旦你理解了什么是 rebase,最重要的是要学习什么时候不能使⽤它。 git rebase 的⻩⾦法则有三: 1. 永远不要 在 公共分⽀ 上使⽤它
2.4 Rebase 的⻩⾦法则 2. 永远不要 在 公共分⽀ 上使⽤它 3. 永远不要 在 公共分⽀ 上使⽤它
试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么
试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么
试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master
分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。
试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master
分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。 此时,同步两个 master 分⽀的唯⼀⽅法是将它们 merge 在⼀ 起,但是这样会产⽣额外的合并提交和两组包含相同更改的提交 (原始提交和通过 rebase 更改的提交)。 这是⼀个令⼈⾮常困惑的情况。
试想⼀下,如果你将 master 分⽀ rebase 到 feature 分⽀之上会发⽣什么 rebase 将所有 master
分⽀上的提交移动 feature 分⽀ 的顶端。 问题是这只发⽣在 你⾃⼰ 的存储库中,所有其他开发 ⼈员仍在使⽤原始版本的 master。 由于 rebase 导致全新 commit,Git 会认为你的 master 分⽀历史与其他⼈的历史不同。 此时,同步两个 master 分⽀的唯⼀⽅法是将它们 merge 在⼀ 起,但是这样会产⽣额外的合并提交和两组包含相同更改的提交 (原始提交和通过 rebase 更改的提交)。 这是⼀个令⼈⾮常困惑的情况。 因此,在运⾏ git rebase 命令之前,总是问⾃⼰,还有其他⼈在⽤这个分⽀吗? 如果答案是肯定的,那就把你的⼿从键盘上移开,开始考虑采⽤⾮破坏性的⽅式进 ⾏改变(例如 git merge)。
永远不要 在 公共分⽀ 上使⽤ Rebase 2.5 Rebase 的⼏种场景
永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) git
checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) •
将上游更改合并到功能分⽀中 git checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
永远不要 在 公共分⽀ 上使⽤ Rebase • 本地 commit 整理(压缩、修改) •
将上游更改合并到功能分⽀中 • 在提交 PR 之前清理代码 git checkout feature git rebase -i HEAD~3 2.5 Rebase 的⼏种场景
2.6 总结 如果你更喜欢⼲净的合并提交,完美的线性历史记录,你就需要开始了解并使⽤ git rebase。 如果你想保留项⽬的完整历史记录并避免重写公共提交的⻛险,你可以坚持下去 git merge。 这两种选择都是完全有效的。
3. 回滚
3.1 概述 Reset 与 Revert 这两个命令都能实现代码库的回滚操作,但两者的实现⽅式是有本质区别的。 • Reset 是直接删除指定的 commit
• Revert 是⽤⼀次新的 commit 来对之前的 commit 做⼀次“逆”操作
3.1.1 Reset 例如 hotfix 分⽀上最近两次提交都不要了
3.1.1 Reset 例如 hotfix 分⽀上最近两次提交都不要了 git checkout hotfix git reset
HEAD~2
https://learngitbranching.js.org/
https://learngitbranching.js.org/
Git Reset 的三种模式 --soft、--mixed、--hard •--soft:仅重置提交区(commit) •--mixed:重置提交区(commit)和暂存区 (staged) •--hard:重置提交区(commit)和暂存区 (staged),并使⽤ commit
的内容填充⼯ 作区(working directory)
3.1.2 Revert
3.1.2 Revert 假如 V1 的修改不需要了
3.1.2 Revert git checkout feature git revert --no-commit HEAD~2 假如
V1 的修改不需要了
假如 V2、V3 的修改不需要了
git checkout feature git revert --no-commit HEAD~2..HEAD 假如 V2、V3 的修改不需要了
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
对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m
参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码
对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m
参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码
对于由 Three-Way Merge 产⽣的 merge commit, 在 revert 时需要加 -m
参数,例如: 某个功能不上了,我们要从 master ⾥剔除已合并 的 feature 分⽀的代码 git revert -m 1 M1 对 M1 进⾏ revert (此时 M1 有两个 parent,所以我们需要使⽤ -m 或 --mainline 告诉 git 我们保留那个分⽀)
对于由 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 我们保留那个分⽀)
对于由 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 我们保留那个分⽀)
3.2 总结 在运⾏ git reset 改写历史之前,和使⽤ git rebase ⼀样,总是问⾃⼰: 还有其他⼈在⽤这个分⽀吗?
如果答案是肯定的,那不要敲下回⻋键,⽽是优先考虑采⽤ git revert。 注:git reset 和 git rebase 在改写历史的时候均需要配合 force push 使⽤,所以公共分⽀均应开启分⽀保护以规 避被改写历史的问题。
练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? text.txt commits
练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? Revert :注意 merge commit,另外需解冲突 text.txt commits
练练⼿ 有这么⼀段提交记录,现在想只保留 1、2 的提交,在不改写历史的情况下,如何做? Revert :注意 merge commit,另外需解冲突 Reset:也可以不改写历史 text.txt
commits
练练⼿ 有这么⼀段提交记录,现在想只保留 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
4. 其他
4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免
MR 在准备就绪前被合并
4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免
MR 在准备就绪前被合并 使⽤:
4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免
MR 在准备就绪前被合并 使⽤: • 在 MR 的标题开头添加 WIP:
4.1 WIP MR Work In Progress Merge Request 含义:在⼯作过程中的合并请求 作⽤:避免
MR 在准备就绪前被合并 使⽤: • 在 MR 的标题开头添加 WIP: • 当已经准备好被合并,编辑⼯单来⼿动删除 WIP: 或者使⽤“Resolve WIP status”按钮
4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做?
4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交
4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤
IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout)
4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤
IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout) 3. 可以使⽤ git stash 命令暂存未提交的修改
4.2 Stash 程序员的⾄宝 情景⼀:正在开发⼀个项⽬还未完成,这时有⼀个更为紧要的问题处理,需要切换到其他分⽀。 情景⼆:当把新功能开发完成的时候,忽然发现⾃⼰开发使⽤的分⽀是错误的分⽀。 为了不丢失正在开发的代码,你会怎么做? 1. 可以将未完成的代码提交,但也因此增加⼀个多余提交 2. 可以⽤
IDE 来 checkout(例如 IDEA 会提示 Smart Checkout or Force Checkout) 3. 可以使⽤ git stash 命令暂存未提交的修改 git stash 的具体命令可以⾃⾏搜索,也可以直接使⽤图形化的 GUI 简化命令操作。
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,⾮常难看。
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,⾮常难看。
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 的⼀个典型场景:
4.4 Cherry Pick 摘樱桃 使⽤ cherry-pick,可以从其他分⽀复制指定的提交,然后导⼊到现在的分⽀。 主要使⽤的场景: • 把弄错分⽀的提交移动到正确的地⽅ •
把其他分⽀的提交添加到现在的分⽀ 例如: a - b - c - d Master \ e - f - g Feature
4.4 Cherry Pick 摘樱桃 使⽤ cherry-pick,可以从其他分⽀复制指定的提交,然后导⼊到现在的分⽀。 主要使⽤的场景: • 把弄错分⽀的提交移动到正确的地⽅ •
把其他分⽀的提交添加到现在的分⽀ git checkout master git cherry-pick f 例如: a - b - c - d - f Master \ e - f - g Feature
Q&A
Thanks