Git 是程序员日常开发中使用频率最高的版本控制工具,但它的命令体系却让很多人又爱又恨。尤其是涉及分支操作时——merge 和 rebase 该怎么选?reset 和 revert 有什么区别?HEAD 指针到底在指向哪里?

这些问题的本质,都在于对 Git 底层提交树的变化缺乏直观理解。本文将从提交记录、分支、合并、变基、撤销操作等核心概念入手,系统梳理 Git 最常用的命令及其背后的工作原理。

💡 顺便推荐一个学习神器 —— LearnGitBranching,它是一个交互式可视化工具,你输入命令的同时能实时看到提交树的变化。文中部分示例图灵感来源于此,强烈建议你打开 Learn Git Branching 同步操作,效果加倍!


📘 主要篇(本地操作)—— 完整详细知识全解

第一部分:基础篇

对应关卡:主要 > 基础篇(共 4 关)

学习目标:掌握 Git 最核心的四个基本操作 —— 提交、分支、合并、变基。这是所有后续操作的地基。

知识点 1.1:git commit —— 提交记录

场景描述:你在项目中完成了一个功能模块的开发,需要把这次变更保存到版本库中。

核心理解:每次执行 git commit,Git 都会对当前暂存区的文件生成一个完整的快照,并打包成一个不可变的提交节点(Commit Node)。这个节点拥有唯一的 SHA-1 哈希值作为身份证。同时,当前分支指针会自动向前移动一位,指向这个新生成的提交。

基本用法

# 两步走:先添加,后提交(最标准的方式)
git add .                    # 将所有变更添加到暂存区
git commit -m "feat: 完成用户登录模块"

# 一步到位:跳过暂存区(仅对已跟踪的文件有效)
git commit -a -m "fix: 修复登录超时bug"

# 查看提交历史,确认新节点已生成
git log --oneline

知识点 1.2:git branch —— 分支的创建

场景描述:你准备开发一个新功能,但又不想影响主线的稳定性。这时需要创建一个独立的分支。

核心理解:Git 中的分支本质上只是一个轻量级的指针(存储为 41 字节的文件)。它仅仅指向某一个具体的提交记录。创建分支的成本极低(几乎是瞬间完成),因此 Git 鼓励频繁创建分支

💡 重要认知:执行 git branch <分支名> 仅仅是在当前 HEAD 指向的提交上贴了一张新的便利贴(创建新指针),并不会自动切换过去。你依然站在原地。

基本用法

# 查看所有本地分支(当前所在分支前会有 * 号)
git branch

# 创建一个名为 feature-login 的新分支(停留在当前分支)
git branch feature-login

# 创建分支并立即切换过去(最常用的组合命令)
git checkout -b feature-login

# Git 2.23+ 推荐使用 switch(语义更清晰)
git switch -c feature-login

知识点 1.3:git merge —— 分支合并(保留分叉历史)

场景描述:你在 feature-login 分支上开发完成了登录功能,现在需要将它合并回主干 main 分支,让其他人也能用到这个功能。

核心理解git merge 会找到两个分支的共同祖先,然后将目标分支上的所有变更整合到当前分支。如果两个分支存在分叉(各自有独有的提交),Git 会执行三方合并,并生成一个拥有两个父节点的特殊合并提交。

合并的两种情况

情况 条件 结果 是否生成合并提交

快进合并

(Fast-forward)

当前分支是目标分支的直接祖先 当前分支指针直接向前移动,追上目标分支 ❌ 不生成

三方合并

(3-way Merge)

两个分支有分叉(共同祖先较早) 创建一个新的合并提交,将两条分支的历史汇聚 ✅ 生成一个新节点

基本用法

# 1. 先切换到你要合入的目标分支(通常是 main 或 dev)
git checkout main

# 2. 执行合并命令,将 feature-login 的变更合入当前分支
git merge feature-login

# 合并成功后,可以安全删除本地的功能分支
git branch -d feature-login

知识点 1.4:git rebase —— 变基合并(线性整洁历史)

场景描述:你希望提交历史保持一条干净的直线,便于代码审查和问题追溯。此时可以选择变基(Rebase)来代替合并。

核心理解:Rebase 的核心是改变基底。它会找到当前分支与目标分支的共同祖先,然后把当前分支上独有的提交逐个复制到目标分支的最顶端。原提交节点虽然依然存在(在 LearnGitBranching 中显示为半透明的灰色节点),但分支指针已经指向了新复制的提交节点(拥有全新的哈希值)。

⚠️ 黄金法则不要对已经推送到远程仓库的公共分支执行 rebase!因为 rebase 会改写提交哈希值,导致团队成员的本地历史与远程历史产生冲突。

基本用法

# 将当前分支(如 feature-login)变基到 main 分支之上
git rebase main

# 等价写法:明确指定源分支和目标分支
git rebase main feature-login

# 变基完成后,提交历史是一条直线
git log --oneline --graph

🔥 基础篇总结:Merge vs Rebase 怎么选?

场景 推荐方案 原因
合并公共分支(如 feature → main) git merge 保留真实开发时间线,队友容易追溯
整理本地特性分支(如多个 WIP 提交) git rebase 让本地历史清晰整洁,方便后续合并
多人协作的长期分支 git merge 避免改写历史带来的同步灾难
个人本地实验分支 git rebase 可以任意整理,不影响他人

第二部分:高级篇

对应关卡:主要 > 高级篇(共 3 关)

学习目标:掌握 HEAD 的本质、相对引用的灵活移动、以及安全的撤销变更方式。

知识点 2.1:分离 HEAD

场景描述:你不想通过分支来查看代码,而是想直接切换到某个历史提交去调试或查看当时的代码状态。

核心理解:通常 HEAD 是指向一个分支(如 main),而分支再指向具体的提交。但当我们执行 git checkout <提交哈希> 时,HEAD 就会直接指向某个具体的提交节点,而不再依附于任何分支。这种状态被称为 “分离 HEAD” 状态。

⚠️ 注意:在分离 HEAD 状态下进行新的提交,这个提交将不属于任何分支。如果你切换回其他分支,这个提交将变得难以找到(除非记住它的哈希值)。所以分离 HEAD 通常只用于浏览代码,而非开发。

基本用法

# 查看提交日志,找到目标提交的哈希值
git log --oneline

# 输出示例:
# a1b2c3d (HEAD -> main) 第三次提交
# e4f5g6h 第二次提交
# i7j8k9l 第一次提交

# 让 HEAD 直接指向第二次提交(进入分离 HEAD 状态)
git checkout e4f5g6h

# 此时再执行 git log,你会发现 HEAD 不再指向 main
git log --oneline

知识点 2.2:相对引用 —— ^ 和 ~

场景描述:你不想每次都去复制粘贴一长串哈希值,想通过相对位置来快速定位某个历史提交。

核心理解:Git 提供了两个非常强大的相对引用操作符:

操作符 含义 示例 解释
^ 向上移动 1 个父节点 main^ main 分支的父提交
~<num> 向上移动 N 个父节点 HEAD~4 HEAD 之前的第 4 级父提交

💡 组合使用:git checkout main^^ 表示从 main 出发,向上移动 2 步。git checkout HEAD~3^2 表示先向上移动 3 步,再切换到第二个父节点(适用于合并提交)。

基本用法

# 切换到 main 分支的父提交
git checkout main^

# 切换到当前 HEAD 之前的第 3 个提交
git checkout HEAD~3

# 将 main 分支强制移动到其祖父提交(相当于回退两步)
git branch -f main HEAD~2

# 在 LearnGitBranching 沙盒中,你可以用相对引用快速定位任意节点

知识点 2.3:撤销变更 —— git reset vs git revert

场景描述:你提交了一个错误代码,需要撤销这次变更。但根据“是否已推送到远程”,撤销方式完全不同。

核心理解

命令 操作本质 是否改写历史 安全性 适用场景
git reset 将分支指针回退到指定提交,丢弃之后的所有提交 ✅ 是(本地历史被改写) ⚠️ 中(丢失未提交的更改风险) 仅限本地实验分支
git revert 生成一个新的提交,其内容正好与目标提交的更改相反 ❌ 否(只追加新记录) ✅ 高 已推送到远程的公共分支

基本用法

# ========== git reset(本地回退,慎用!) ==========

# 查看提交历史
git log --oneline
# a1b2c3d (HEAD -> main) 第三次提交
# e4f5g6h 第二次提交
# i7j8k9l 第一次提交

# 回退到第二次提交(第三次提交被丢弃,且文件状态也回退)
git reset --hard e4f5g6h

# reset 有三种模式(常用的是 --hard 和 --soft)
git reset --soft <提交>    # 只移动 HEAD,暂存区和工作区不变
git reset --mixed <提交>   # 移动 HEAD + 重置暂存区(默认)
git reset --hard <提交>    # 移动 HEAD + 重置暂存区 + 重置工作区(危险!)


# ========== git revert(安全撤销,推荐!) ==========

# 撤销第三次提交,生成一个新的反向提交
git revert a1b2c3d

# 执行后,git log 会看到多了一个新提交,其内容与 a1b2c3d 正好相反
git log --oneline
# f6g7h8i (HEAD -> main) Revert "第三次提交"
# a1b2c3d 第三次提交
# e4f5g6h 第二次提交

第三部分:移动提交记录

对应关卡:主要 > 移动提交记录(共 2 关)

学习目标:掌握 cherry-pick 和交互式 rebase,精准控制哪些提交需要“搬移”。

知识点 3.1:git cherry-pick —— 精准摘取提交

场景描述:你发现别的分支上有个单独的提交修复了你当前分支的一个 bug,你只想把这个提交拿过来,而不想合并整个分支。

核心理解cherry-pick 可以将任意一个或多个指定的提交,按其顺序复制到当前分支的顶端。这是最直接、最精准的“拿来主义”操作,非常适合热修复跨分支取补丁的场景。

基本用法

# 查看目标提交的哈希值(在其他分支上)
git log other-branch --oneline

# 将指定的提交复制到当前分支
git cherry-pick a1b2c3d

# 一次性摘取多个提交(按从左到右的顺序依次应用)
git cherry-pick a1b2c3d e4f5g6h i7j8k9l

# 如果发生冲突,解决后执行:
git add .
git cherry-pick --continue

# 放弃本次 cherry-pick
git cherry-pick --abort

知识点 3.2:交互式 Rebase —— git rebase -i

场景描述:你的分支上有 5 个乱糟糟的提交(比如 "wip"、"fix typo"、"temp"),你希望在合并到主分支之前,整理成一组干净、有意义的提交记录

核心理解:交互式 Rebase 会打开一个文本编辑器,让你对最近 N 个提交执行以下操作:

指令 含义 使用场景
pick 保留该提交(默认) 正常保留
reword 保留提交内容,修改提交信息 把 "wip" 改成 "feat: 完成支付接口"
edit 保留但暂停,允许修改内容 需要补充文件到该提交中
squash 将该提交合并到前一个提交中 将多个细碎的提交压缩成一个
drop 删除该提交 丢弃无用的实验提交
reorder(调整顺序) 调整提交顺序 让提交按逻辑顺序排列

基本用法

# 对最近 3 次提交进行交互式整理
git rebase -i HEAD~3

# 此时会弹出编辑器,内容类似:
# pick a1b2c3d wip: 登录模块
# pick e4f5g6h fix typo
# pick i7j8k9l temp: 测试代码
# 
# 在编辑器内通过剪切、粘贴(或键盘命令)调整行的上下顺序,
# 或将 "pick" 改为 "squash" / "reword",保存退出即可生效。

# 更常见的是:将当前分支变基到 main,并整理其中的提交
git rebase -i main

第四部分:杂项

对应关卡:主要 > 杂项(共 5 关)

学习目标:掌握一些“小而美”的实用技巧,包括精准取提交、修正提交、打标签和描述提交。

知识点 4.1:只取一个提交记录

场景描述:在一个庞大的分支中,你只想要其中某一个特定的提交,而 cherry-pick 需要你知道哈希值,但通过交互式 rebase 配合 drop 可以批量过滤。

核心理解:这是对 cherry-pick 和 交互式 rebase 的综合运用。要“只取一个”,有两种思路:

  1. Cherry-pick 直接拿:如果知道哈希值,直接 git cherry-pick <hash>

  2. 交互式 Rebase 过滤:如果在某个范围内,用 git rebase -i 将不需要的提交标记为 drop,只保留目标提交。

基本用法

# 方法一:直接摘取(最精准)
git cherry-pick target-hash

# 方法二:通过交互式 rebase 保留唯一目标
git rebase -i HEAD~5
# 在编辑器中,将除了目标提交之外的所有提交都改为 drop

知识点 4.2:提交技巧 #1 —— git commit --amend

场景描述:你刚刚执行了 git commit -m "完成登录",突然发现漏掉了一个小文件,或者提交信息写错了。你不想生成一个“多余的”新提交(比如 "fix typo"),希望修正上一次提交

核心理解git commit --amend 会将暂存区中的内容和上一次提交的内容合并,并生成一个全新的提交节点(拥有新的哈希值) 来替换掉上一次提交。旧的提交在仓库中依然存在(变成悬空对象),但不再被任何分支引用。

⚠️ 注意amend 本质上也改写了历史(替换了提交节点),如果上次提交已经 push 到远程,请谨慎使用。

基本用法

# 1. 补充漏掉的文件到暂存区
git add forgot_file.txt

# 2. 修正上一次提交(注意:这会生成一个【全新的哈希值】来替换旧提交)
git commit --amend -m "feat: 完成登录模块(含验证码)"

# 如果只想修改提交信息,不改变内容
git commit --amend --only -m "新的提交信息"

# 查看日志,会发现旧的提交哈希已经消失了,取而代之的是一个全新的哈希值
git log --oneline

知识点 4.3:提交技巧 #2 —— 交互式 Rebase 调整顺序

场景描述:你有三个提交,但它们的逻辑顺序是混乱的(比如先写了 bug 修复,后写了新功能)。你希望调整它们的先后顺序,让历史更符合逻辑。

核心理解:在交互式 Rebase 的编辑器中,你可以直接调整行的上下顺序。Git 会按照你调整后的顺序,从上到下依次重新应用这些提交。

基本用法

# 对最近 3 次提交进行重排
git rebase -i HEAD~3

# 编辑器中的内容(调整前):
# pick c3d4e5f 修复支付bug
# pick a1b2c3d 新增支付接口
# pick e4f5g6h 补充单元测试
#
# 在编辑器内通过剪切、粘贴手动调整顺序为:
# pick a1b2c3d 新增支付接口
# pick e4f5g6h 补充单元测试
# pick c3d4e5f 修复支付bug
#
# 保存退出后,提交顺序即被重排

知识点 4.4:git tag —— 打标签(不可变指针)

场景描述:项目发布了一个正式版本(如 v2.1.0),你希望永久标记这个提交,以便将来随时回溯。这个标记不受后续提交的影响。

核心理解:Tag(标签)和 Branch(分支)都是指向提交的指针。但最大的区别在于:分支指针会随新提交移动,而 Tag 指针一旦创建就永远固定在那个提交上,不会移动。

基本用法

# 创建轻量标签(仅一个指针)
git tag v2.1.0

# 创建附注标签(包含作者、日期、说明信息,推荐)
git tag -a v2.1.0 -m "正式发布 v2.1.0 版本,修复了登录超时问题"

# 给历史提交打标签(不一定是当前 HEAD)
git tag v1.0.0 a1b2c3d

# 查看所有标签
git tag

# 查看某个标签的详细信息
git show v2.1.0

# 推送标签到远程仓库(默认不会自动推送)
git push origin v2.1.0
git push origin --tags   # 推送所有本地标签

知识点 4.5:git describe —— 描述提交

场景描述:你在调试时处于一个不知道具体哈希值的提交上,你想快速了解这个提交距离最近的一个标签有多远

核心理解git describe 会找到离当前提交最近的可达附注标签(即从当前提交回溯能碰到的第一个标签),然后输出描述信息。输出格式为:

<标签名>-<距离>-g<提交哈希前7位>
  • <标签名>:最近的可达标签

  • <距离>:从标签到当前提交中间隔了多少个提交

  • g<哈希>:当前提交的 SHA-1 缩写(g 代表 Git)

⚠️ 特别注意git describe 默认只查找“附注标签”(即 git tag -a 创建的)。如果仓库中只有“轻量标签”(Lightweight Tags),直接执行会报错(fatal: No names found)。若要匹配轻量标签,必须加上 --tags 参数。

基本用法

# 默认只查找附注标签(git tag -a 创建的)
git describe
# 输出示例:v2.1.0-3-g1a2b3c4
# 含义:当前提交在 v2.1.0 标签之后,又经过了 3 次提交,当前哈希为 1a2b3c4

# 若要匹配轻量标签,必须加上 --tags
git describe --tags

# 描述指定的提交
git describe a1b2c3d

# 如果当前提交正好就是标签本身,输出只显示标签名
git describe v2.1.0
# 输出:v2.1.0

第五部分:高级话题

对应关卡:主要 > 高级话题(共 3 关)

学习目标:应对复杂分支场景 —— 链式变基、合并提交的双父节点操作、以及复杂依赖下的分支迁移。

知识点 5.1:多次 Rebase(链式变基)

场景描述:你在本地开发时,需要将分支依次变基到不同的目标分支上(例如:先变基到 dev,再变基到 main)。

核心理解:Rebase 可以连续执行多次。每一次 rebase 都会将当前分支的提交复制到新的目标顶端,并丢弃旧的复制链。掌握链式变基,可以灵活地将分支“嫁接”到任意的基底上。

基本用法

# 假设当前分支是 feature,需要先变基到 dev,再变基到 main

# 第一步:将 feature 变基到 dev
git rebase dev feature

# 第二步:将 feature 变基到 main(此时 feature 的提交已更新)
git rebase main feature

# 合并查看,提交历史是一条从 main 出发的直线
git log --oneline --graph --all

知识点 5.2:两个 parent 节点 —— ^1 与 ^2 的妙用

场景描述:你站在一个合并提交(Merge Commit)上,这个提交有两个父节点。你想切换到另一个父节点所在的分支(即合并时被合入的那个分支),而不是默认的第一个父节点。

核心理解:合并提交具有两个父节点

  • 第一个父节点(^1:执行 git merge 时当前所在的分支(即被合入的目标分支,通常是 main)

  • 第二个父节点(^2:执行 git merge 时被合并的分支(即 git merge <分支名> 中的那个分支)

在相对引用中,^ 默认等同于 ^1,而 ^2 可以让我们切换到合并提交的另一个分支

基本用法

# 假设我们在一个合并提交上,HEAD 指向 merge commit
git log --oneline
# f6g7h8i (HEAD -> main) Merge branch 'feature-login'

# 切换到第一个父节点(通常是 main 合并前的状态)
git checkout HEAD^1

# 切换到第二个父节点(被合并的 feature-login 分支的最新提交)
git checkout HEAD^2

# 也可以在相对引用中组合使用
git checkout HEAD~3^2   # 先向上 3 步,再切换到第二个父节点

知识点 5.3:纠缠不清的分支 —— 综合实战

场景描述:在真实的开发过程中,多个分支之间可能存在复杂的交叉依赖,例如:分支 A 依赖分支 B 的某个提交,而分支 B 又依赖分支 C。你需要在不破坏依赖关系的前提下,将特定的分支移动到新的基底上。

核心理解:这是一个综合性实战场景,要求灵活运用前面学过的所有技能:

  • 相对引用^~)快速定位

  • Cherry-pick 精准摘取关键提交

  • Rebase 整体搬移分支

  • 分支强制移动git branch -f)修正指针位置

基本用法(策略思路)

# 场景:假设有三个分支 feature-a, feature-b, feature-c,它们纠缠在一起

# 策略1:先识别关键提交,用 cherry-pick 提取必要部分
git checkout feature-a
git cherry-pick feature-b~2   # 摘取 feature-b 之前的特定提交

# 策略2:用交互式 rebase 清理历史
git rebase -i HEAD~4          # 整理当前分支的提交顺序

# 策略3:用 git branch -f 强制移动分支指针到正确位置
git branch -f feature-c main~3

# 策略4:链式变基解决依赖
git rebase feature-c feature-b
git rebase feature-b feature-a

# 最终检查提交树结构
git log --oneline --graph --all

📘 远程篇(远程仓库操作)—— 完整详细知识全解

第一部分:Push & Pull —— Git 远程仓库!

对应关卡:远程 > Push & Pull(共 7 个核心知识节点)

学习目标:掌握 Git 远程仓库的完整工作流,理解本地仓库与远程仓库(origin)之间的数据同步机制。

知识点 1.1:git clone —— 克隆远程仓库

场景描述:你刚加入一个新团队,需要将远程服务器上的项目代码完整地下载到自己的电脑上,并自动建立与远程仓库的关联。

核心理解git clone 是一个复合操作,它一次性完成了三件事:

  1. 下载:将远程仓库的所有提交记录、分支、文件完整下载到本地。

  2. 创建本地分支:在本地创建一个 main(或 master)分支,并自动关联到远程的 main 分支。

  3. 记录远程地址:自动将远程仓库命名为 origin(默认别名),并保存其地址。

💡 关键认知:克隆完成后,本地会多出一个名为 origin/main 的远程追踪分支(只读指针),它代表了“上次与远程仓库同步时,远程 main 分支的位置”。

基本用法

# 通过 HTTPS 克隆(需要输入账号密码/令牌)
git clone https://github.com/username/repository.git

# 通过 SSH 克隆(推荐,配置好密钥后免密)
git clone git@github.com:username/repository.git

# 克隆并指定本地文件夹名称
git clone https://github.com/username/repository.git my-project

# 克隆指定分支(默认克隆所有分支,但只检出 main)
git clone -b feature-login https://github.com/username/repository.git

知识点 1.2:远程分支 —— origin 与远程追踪分支

场景描述:你克隆完项目后,想查看远程仓库有哪些分支,以及本地分支和远程分支的对应关系。

核心理解:远程分支的完整命名格式是 <远程仓库名>/<分支名>(如 origin/main)。它们本质上是只读的指针,存储在本地仓库中,用于记录上一次与远程仓库通信时,远程分支所处的位置。

⚠️ 重要:你不能直接在 origin/main 上进行提交。要更新它,只能通过 git fetchgit pull 或成功的 git push

基本用法

# 查看所有本地分支和远程追踪分支
git branch -a

# 输出示例:
# * main
#   feature-login
#   remotes/origin/main
#   remotes/origin/dev
#   remotes/origin/feature-login

# 查看远程仓库的详细信息(URL 和跟踪分支)
git remote show origin

# 仅查看远程分支列表
git branch -r

知识点 1.3:git fetch —— 获取远程更新(只下载,不合并)

场景描述:你的同事刚刚往远程仓库推送了新代码,你想先把这些更新下载到本地看看,但暂时不想合并到自己的工作分支中。

核心理解git fetch 会从远程仓库下载本地没有的提交记录和文件,并更新本地的远程追踪分支(如 origin/main)。但请注意:它不会修改你的工作目录,也不会移动你本地的 main 分支指针。这是一个绝对安全的只读操作。

基本用法

# 获取所有远程分支的更新
git fetch

# 仅获取指定远程仓库的更新
git fetch origin

# 仅获取指定远程分支的更新
git fetch origin main

# 获取并同时删除本地已经不存在的远程分支引用(清理)
git fetch --prune

知识点 1.4:git pull —— 拉取并合并(fetch + merge)

场景描述:你想一步到位,将远程仓库的最新代码下载并合并到当前本地分支中。

核心理解git pull 等价于 git fetch + git merge 的组合命令。它会先下载远程更新,然后将远程追踪分支(如 origin/main合并到当前的本地分支(如 main)。

💡 小贴士:如果你偏好线性历史,可以配置 git pull --rebase(等价于 fetch + rebase),但默认情况下是 merge

基本用法

# 拉取并合并(默认)
git pull

# 拉取指定远程仓库的指定分支
git pull origin main

# 拉取并使用变基方式合并(保持线性历史)
git pull --rebase origin main

# 查看 git pull 的实际执行细节
git pull --verbose

知识点 1.5:模拟团队合作 —— 远程分支的竞争状态

场景描述:你和同事同时基于同一个远程提交进行开发。你先完成了工作并推送成功,而同事在推送时发现远程已经被你更新了。

核心理解:这关是为了模拟真实的多人协作冲突场景。你的本地提交与远程分支产生了分叉(Divergence)。此时直接 git push 会被拒绝,因为远程分支已经不是你上次拉取时的状态了。

基本用法(模拟场景)

# 假设你基于 o/main 的节点 A 做了本地提交 C1
git commit -m "feat: 本地新增功能"

# 此时远程仓库被同事更新到了节点 C2(模拟)
# 直接推送会报错:! [rejected] main -> main (fetch first)
git push origin main

知识点 1.6:git push —— 推送本地提交到远程

场景描述:你在本地完成了功能开发,测试通过后,需要将代码推送到远程仓库,让团队成员能够看到或使用你的成果。

核心理解git push 会将本地分支上的新提交上传到远程仓库,并更新远程仓库对应的分支指针。同时,本地的远程追踪分支(如 origin/main)也会随之更新。

基本用法

# 将当前分支推送到同名的远程分支(最常用)
git push origin main

# 将本地 feature 分支推送到远程的 feature 分支
git push origin feature

# 简写(如果已经设置了上游追踪)
git push

# 将本地分支推送到远程的不同名称分支
git push origin local-branch-name:remote-branch-name

知识点 1.7:处理偏离的提交历史 —— 推送被拒的解决方案

(涵盖你提到的两个“偏离的提交历史”节点,合并为一个综合解决方案)

场景描述:你执行 git push 时被拒绝了,因为远程分支包含了你本地没有的提交(同事先你一步推送了代码)。你必须先整合远程的更新,才能推送。

核心理解:当本地 main 与 o/main 产生分叉时(即“偏离”状态),Git 不允许你直接推送,因为这会覆盖同事的提交。解决方案有两种思路:

方案 命令组合 效果 适用场景
方案 A:变基后推送 git fetch → git rebase o/main → git push 提交历史呈线性,整洁干净 个人特性分支,追求清晰历史
方案 B:合并后推送 git fetch → git merge o/main → git push 产生合并提交,保留完整分叉 公共分支协作,保留真实时间线

基本用法(方案 A:变基,推荐)

# 1. 获取远程最新状态
git fetch origin

# 2. 将本地提交变基到最新的远程分支之上(改写本地历史)
git rebase origin/main

# 3. 解决可能出现的冲突后,继续变基
git add .
git rebase --continue

# 4. 推送(此时已经是快进推送,不会报错)
git push origin main

基本用法(方案 B:合并)

# 1. 获取远程最新状态
git fetch origin

# 2. 将远程分支合并到本地
git merge origin/main

# 3. 解决冲突并提交合并记录
git add .
git commit -m "merge: 合并远程更新"

# 4. 推送
git push origin main

第二部分:关于 origin 和它的周边 —— Git 远程仓库高级操作

对应关卡:远程 > 关于 origin 和它的周边(共 6 个高级知识节点)

学习目标:深入理解远程追踪机制、灵活使用 RefSpec 参数,掌握高级的远程分支管理技巧。

知识点 2.1:推送主分支 —— 设置默认推送行为

场景描述:你不想每次都输入完整的 git push origin main,希望直接输入 git push 就能正确地推送到远程的指定分支。

核心理解git push 的默认行为取决于 push.default 配置。最常用的配置是 simple(Git 2.0+ 默认),它要求本地分支必须与远程分支同名,且只推送当前分支。而 matching 会推送所有同名的本地分支。

基本用法

# 查看当前的 push 配置
git config --global push.default

# 设置为 simple(推荐,只推送当前分支到同名的远程分支)
git config --global push.default simple

# 设置为 upstream(推送当前分支到其上游追踪分支)
git config --global push.default upstream

# 显式指定远程主分支并推送
git push -u origin main    # -u 表示同时设置 upstream(上游追踪)

知识点 2.2:合并远程仓库 —— 手动合并远程追踪分支

场景描述:你已经执行了 git fetch,现在想手动决定何时将远程的更新合并到本地分支,而不是通过 git pull 一步到位。

核心理解git merge origin/main 可以将本地存储的远程追踪分支(origin/main)合并到当前分支。相比 git pull,这种方式给了你更精细的控制权,可以在合并前先检查远程更新的内容。

基本用法

# 1. 先获取远程更新(不做合并)
git fetch origin

# 2. 查看远程更新了什么(对比差异)
git log HEAD..origin/main --oneline

# 3. 手动将远程分支合并到当前分支
git merge origin/main

# 如果只想查看远程分支的代码状态(不合并),可以临时切换过去
git checkout origin/main   # 进入分离 HEAD 状态,只读查看

知识点 2.3:远程追踪 —— 上游分支的绑定与管理

场景描述:你创建了一个新的本地分支 feature-payment,想要让它“跟踪”远程的某个分支,这样每次 git pull 或 git push 时就不用反复指定远程分支名了。

核心理解远程追踪(Upstream/Tracking)是指本地分支与远程分支之间建立的关联关系。建立关联后:

  • git pull 自动从关联的远程分支拉取。

  • git push 自动推送到关联的远程分支。

  • git status 会提示你本地分支与远程分支的领先/落后情况(如 “Your branch is ahead of 'origin/main' by 2 commits.”)。

基本用法

# 方法一:推送时直接建立追踪关系(最常用)
git push -u origin feature-payment

# 方法二:为已存在的本地分支设置上游追踪
git branch --set-upstream-to=origin/main main

# 方法三:创建分支时直接关联(Git 2.23+)
git switch -c feature-payment --track origin/main

# 查看当前分支的追踪关系
git branch -vv

# 输出示例:
# * main        a1b2c3d [origin/main] 第三次提交
#   feature-dev e4f5g6h [origin/dev] 完成开发

知识点 2.4:Git Push 的参数 —— RefSpec 详解(第一部分)

场景描述:你不想推送当前分支,而是想将本地的一个特定分支推送到远程的另一个不同名称的分支上(例如将本地的 hotfix 推送到远程的 main)。

核心理解git push 的标准参数格式是 <远程名> <本地引用>:<远程引用>,这被称为 RefSpec(引用规格)。它允许你灵活地映射本地和远程的分支名称。

基本用法

# 基础格式:推送本地 main 到远程 main
git push origin main:main

# 将本地的 hotfix 分支推送到远程的 main 分支(名字不同)
git push origin hotfix:main

# 删除远程分支(详见 2.5)
git push origin :feature-old

# 将本地当前分支推送到远程的指定分支
git push origin HEAD:main

知识点 2.5:高级 RefSpec —— 没有 source 的 source(删除远程分支)

场景描述:远程仓库有一个已经废弃的旧分支 feature-deprecated,你想在远程把它彻底删除,但本地不需要保留这个分支。

核心理解:当 RefSpec 的 <本地引用>(source)部分留空 时,Git 会理解为你向远程推送了一个 “空”,从而删除远程的对应分支。这是最安全的远程分支删除方式。

基本用法

# 删除远程的 feature-deprecated 分支
git push origin :feature-deprecated

# 等效的替代写法(更直观)
git push origin --delete feature-deprecated

# 删除远程的 main 分支(危险操作,通常需要权限)
git push origin --delete main

知识点 2.6:Git Pull 的参数 —— Pull 的 RefSpec 与底层机制

场景描述:你想从远程仓库的特定分支拉取更新,并将其合并到本地的另一个不同名称的分支中。或者,你只想拉取,但不合并到当前分支。

核心理解git pull 的参数语法与 git push 类似,也是 <远程名> <远程引用>:<本地引用>。它的本质是:先 fetch 指定的远程引用,然后再 merge 到指定的本地引用。

⚠️ 注意git pull 的 RefSpec 写法中,<本地引用> 通常就是当前分支,如果省略,则合并到当前分支。

基本用法

# 基础用法:从远程 origin 的 main 分支拉取,合并到本地的 main 分支
git pull origin main:main

# 最常用的简写(合并到当前分支)
git pull origin main

# 拉取远程的 main 分支,但合并到本地的 dev 分支(需先切换到 dev)
git checkout dev
git pull origin main:dev

# 只拉取,不自动合并(相当于 fetch + 手动处理)
git pull --no-commit origin main   # 拉取但不自动提交合并结果

# 使用变基方式拉取(保持线性历史)
git pull --rebase origin main

🔥 远程篇总结:标准团队协作工作流

以下是基于以上所有知识点的最佳实践工作流,适用于绝大多数团队协作场景:

# 1. 克隆项目(首次)
git clone git@github.com:team/project.git
cd project

# 2. 创建并切换到功能分支
git switch -c feature-new-module

# 3. 开发过程中,定期拉取主分支的更新(避免偏离太远)
git fetch origin
git rebase origin/main   # 或 git merge origin/main

# 4. 完成开发,推送到远程(并建立追踪关系)
git push -u origin feature-new-module

# 5. 合并到主分支(通过 PR/MR 或本地合并)
git checkout main
git pull origin main               # 先拉取最新的远程 main
git merge feature-new-module       # 合并功能分支
git push origin main               # 推送更新
git branch -d feature-new-module   # 删除本地分支
git push origin --delete feature-new-module  # 删除远程分支

以上内容如果只看文字还不够过瘾,强烈推荐配合 LearnGitBranching 这个可视化工具实操一遍。它把每一个命令的执行过程都以动画形式呈现在提交树上,尤其适合理解 merge 和 rebase 这类容易混淆的操作。

📘 配套练习资源

  • 交互式可视化工具:LearnGitBranching —— 建议至少通关「基础篇」全部关卡

  • 官方文档:Git 官方中文手册

  • 本地沙盒:在自己的项目中用 git init 创建测试仓库,大胆实验

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐