1, git fetch
和 git pull
之间的区别
git fetch
:只下载,不合并。它从远程仓库获取最新的数据,让你可以在本地查看这些变更,但不会动你当前的工作。这是一个相对“安全”的操作。git pull
:下载并合并。它等同于git fetch
+git merge
。它会抓取远程的变更,并立刻尝试将这些变更合并到你当前所在的本地分支中。
2, 当想要撤销上一次的提交(commit)时,可以使用git reset
和 git revert
之间的区别
git reset
会重置工作区,这只在特定模式下(--hard
)成立。git reset
的核心作用是移动当前分支的 HEAD
指针到另一个提交。它有三个主要模式:
--soft
:只移动HEAD
指针。你的工作区和暂存区(index)的改动都还保留着。--mixed
(默认模式):移动HEAD
指针,并且重置暂存区。你的工作区代码不变,但所有改动都变为“未暂存”状态。--hard
:移动HEAD
指针,重置暂存区,并且重置工作区。这是一个比较危险的操作,因为它会丢弃你所有未提交的本地改动。
而 git revert
并非简单地“重置记录”。它实际上会创建一个新的提交,这个新提交的内容刚好是你想撤销的那个提交的反向操作。它并不会删除或修改旧的提交历史。
所以,它们最关键的区别在于:
- 历史记录:
git reset
会改写历史(特别是reset --hard
),让之前的提交看起来像从未发生过。而git revert
则是通过新增一个提交来“抵消”之前的提交,它会保留完整的历史记录。 - 协作安全:正因为
revert
不会改变历史,所以它对于已经推送到远程的、多人协作的分支是安全的。而reset
一个公共分支,会给其他团队成员带来巨大的麻烦,因为你们的历史记录会产生分叉。
简单来说:
git reset
就像是坐上时光机回到过去,抹掉了某个时间点之后的所有痕迹。适用于你自己的私有分支。git revert
就像是发现做错了一件事,然后你再做一件相反的事来弥补。适用于公共的协作分支。
3, git stash
以及使用场景
git stash
的核心作用:
- 暂存(Stash):它会把你当前工作目录和暂存区(Staging Area)中所有未提交的修改(包括已暂存和未暂存的)保存到一个临时的“储藏栈”中。
- 恢复(Clean State):执行
stash
后,你的工作目录会变得干净,就像刚执行完git checkout
一样,没有任何修改。这让你能毫无顾虑地切换分支、拉取更新或执行其他操作。 - 取回(Pop/Apply):当你处理完其他事情,切回原来的分支后,可以使用
git stash pop
或git stash apply
来恢复之前储藏的修改。
git stash pop
:恢复储藏并从储藏栈中删除该记录。git stash apply
:恢复储藏但保留该记录在储藏栈中,方便你应用到其他分支。
总的来说,git stash
是一个强大的工具,用于处理“工作进行到一半被打断”的场景,让你能快速切换上下文,而不用为了切换分支而创建不完整的提交。
4, git cherry-pick
以及使用场景
git cherry-pick
命令的作用就是,将指定的提交(commit)“摘取”过来,应用到当前的分支。
这个过程,它实际上是复制了这个提交的变更内容,然后在当前分支上创建一个全新的提交。这个新提交会拥有一个新的 commit hash,但默认会保留原提交的作者和提交信息。
最典型的使用场景包括:
- 紧急修复(Hotfix):假设你在一个已经发布的
release
分支上修复了一个紧急的 bug,这个修复只有一个 commit。但你的开发团队正在develop
分支上进行新功能开发,也需要这个修复来避免同样的问题。这时,你就可以用cherry-pick
把那个修复 bug 的 commit 单独应用到develop
分支,而无需合并整个release
分支。 - 选择性地合并功能:你在一个功能分支
feature-A
上提交了好几个 commit,但现在另一个分支feature-B
只需要其中一个 commit 所带来的改动。使用cherry-pick
就可以精确地只把那一个 commit 的变更带过来。 - 从错误的分支恢复:你不小心在
main
分支上做了一个本应在feature
分支上的提交。你可以: a. 切换到feature
分支,cherry-pick
那个错误的提交。 b. 切换回main
分支,使用git reset
移除那个错误的提交。
所以,总的来说,cherry-pick
就像一个精确的手术刀,能够精确地复制单个提交,而不是像 merge
或 rebase
那样对整个分支进行操作。
5, git rebase
和 git merge
的区别
git merge
(合并)
git merge
做的事情很简单:它将两个分支的最新快照(C3
和 C4
)以及它们共同的祖先(C2
)进行三方合并,然后创建一个新的、唯一的“合并提交” (Merge Commit)。
过程图示:
假设你的历史记录是这样的:
A---B---C (feature 分支)
/
D---E---F---G (main 分支)
在 main
分支上执行 git merge feature
后,会变成这样:
A---B---C
/ \
D---E---F---G---H (main 分支, H 是合并提交)
特点:
- 保留历史:它会忠实地记录下历史,合并提交
H
非常清楚地表明了“在G
这个节点,我们把C
的内容合并了进来”。你的提交历史图会是一个有分叉、有合并的网络图。 - 非破坏性操作:它不会改变现有分支的任何提交,只是在目标分支上新增一个提交。
- 安全简单:对于已经推送到远程的公共分支,使用
merge
是安全的。
git rebase
(变基)
git rebase
的目标不同,它旨在创造一个更线性的提交历史。它会把你当前分支(feature
)的所有提交,一个一个地在目标分支(main
)的最新提交后面“重放”一遍。
过程图示:
同样,假设你的历史记录是这样的:
A---B---C (feature 分支)
/
D---E---F---G (main 分支)
在 feature
分支上执行 git rebase main
后,会变成这样:
A'--B'--C' (feature 分支)
/
D---E---F---G (main 分支)
发生了什么?
- Git 会找到
feature
分支和main
分支的共同祖先E
。 - 它会“暂存”
feature
分支独有的提交(A
,B
,C
)。 - 然后,它将
feature
分支的指针移动到main
分支的最新提交G
上。 - 最后,它把暂存的提交
A
,B
,C
重新一个一个地应用在G
后面,创建出内容相同但 hash 值全新的提交A'
,B'
,C'
。
特点:
- 线性历史:最终的提交历史是一条直线,看起来非常整洁,好像所有开发都是按顺序依次进行的。
- 重写历史:
rebase
会丢弃原始的提交(A, B, C),并创建全新的提交(A’, B’, C’)。这是一个破坏性的操作。 - 风险:绝对不要在已经推送到远程的公共分支上执行
rebase
。因为你重写了历史,如果其他团队成员基于旧的历史进行了开发,当他们拉取你的新历史时,会造成巨大的混乱。
总结与对比
特性 | git merge |
git rebase |
---|---|---|
历史记录 | 保留真实的分支与合并历史,非线性 | 重写历史,使其变为线性,更整洁 |
新提交 | 创建一个额外的合并提交 | 不创建合并提交,但会创建新的常规提交 |
协作 | 对公共分支安全 | 危险,只应该在自己的私有分支上使用 |
冲突解决 | 在最后只解决一次所有冲突 | 在重放每个提交时,都可能需要解决冲突 |
应该用哪个?
这很大程度上取决于团队的开发规范,但一个普遍被接受的最佳实践是:
- 从公共分支拉取更新时,使用
rebase
: 当你自己的feature
分支落后于main
分支时,你可以在你的feature
分支上执行git pull --rebase origin main
(或者先git fetch
, 再git rebase origin/main
)。这能让你的分支保持在main
分支的最新位置,避免了将来合并时产生不必要的合并提交。 - 将功能合并到公共分支时,使用
merge
: 当你的feature
分支开发完成,准备合并回main
分支时,切换到main
分支,执行git merge feature
。这会保留该功能分支的完整上下文,并通过一个合并提交清晰地记录这次集成。
简单来说:用 rebase
来“追赶”主干道的进度,用 merge
来“汇入”主干道。rebase
只用于尚未分享给别人的本地分支。
“本地用 rebase
,合并用 merge
” 是一个非常流行且高效的工作流。它既能让你在开发时保持清晰的思路,又能让主干历史忠实地记录每一次功能的集成。
6,
Comments on this entry are closed.