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.