Git详细教程

Git 详细教程

目录

  1. 什么是 Git
  2. 核心概念
  3. 安装与配置
  4. 创建仓库
  5. 工作流基础
  6. 查看历史
  7. 撤销操作
  8. 分支与合并
  9. 远程仓库
  10. 储藏 (Stash)
  11. 标签 (Tag)
  12. 变基 (Rebase)
  13. 拣选 (Cherry-pick)
  14. 二分查找 (Bisect)
  15. Git 工作流
  16. 常用命令速查表
  17. 相关工具
  18. 附录:实战练习

1. 什么是 Git

1.1 版本控制系统

版本控制系统 (VCS) 是一种记录文件变化,以便将来可以查阅特定版本的系统。

1
2
3
4
5
传统做法(无版本控制):
论文_v1.doc → 论文_v2.doc → 论文_最终版.doc → 论文_最终版2.doc → 论文_真的最终版.doc

使用 Git:
论文.doc (一个文件,Git 帮你记住每一次保存的历史)

1.2 Git 的特点

  • 分布式:每个开发者都有完整的仓库副本,不依赖中央服务器
  • 快照而非差异:Git 把数据看作对小型文件系统的一系列快照
  • 本地操作:绝大多数操作只需要本地文件和资源
  • 完整性:Git 中所有数据在存储前都计算校验和(SHA-1 哈希),以哈希引用

1.3 Git 的三种状态

Git 有三种主要状态,你的文件可能处于其中之一:

image-20260616134420823

状态 说明
已修改 (Modified) 文件已更改,但尚未存入数据库
已暂存 (Staged) 对当前修改的文件做了标记,使之包含在下次提交中
已提交 (Committed) 数据已安全地保存在本地数据库中

2. 核心概念

2.1 快照 (Snapshot)

每次提交 (commit),Git 会对整个项目文件制作一个快照,并存储这个快照的引用。

1
2
3
4
5
提交历史示意(每个圆圈是一次 commit):

○ ← ○ ← ○ ← ○ ← ○
| | | | |
C1 C2 C3 C4 C5 (C5 是最新的提交)

2.2 SHA-1 哈希

Git 中一切都是用哈希值来索引的,每个提交、文件、目录都有一个 40 位的 SHA-1 哈希:

1
e83c5163316f89bfbde7d9ab23b2e8e1a9e8e9f3

实际使用中,通常取前 7 位即可唯一标识:

1
e83c516

2.3 HEAD、工作树、索引

image-20260616134755076
  • HEAD:指向当前所在分支(或某次特定提交)的指针
  • 工作树/工作目录:你电脑上实际看到的文件
  • 索引/暂存区:下次提交的”蓝图”

2.4 Git 对象模型

Git 是一个内容寻址文件系统,核心是四种对象:

image-20260616140253713

image-20260616140333817

四种 Git 核心底层对象详解

blob(二进制大对象)

  • 存储单个文件完整内容,不存文件名、路径,只存文件原始文本 / 二进制数据
  • 多个同名文件、不同目录,只要内容完全一样,会共用同一个 blob,实现去重存储

tree(树对象)

  • 代表一层目录结构,相当于文件夹快照
  • 内部记录:文件名 / 子目录名 + 对应 blob 哈希 / 子 tree 哈希 + 文件权限
  • 根 tree 就是一次 commit 对应的项目完整目录

commit(提交对象)

  • 绑定一次完整版本快照,保存版本元数据:

    根 tree 哈希、父 commit、作者、提交人、时间、提交备注

  • 每一次git commit都会生成一个全新 commit 对象

tag(标签对象)

  • 轻量 tag 仅存指向 commit 的哈希;
  • 附注 tag(annotated tag)是独立对象,可附带版本说明、签名,用于标记发布版本(v1.0、v2.1)

简单来说就是底下这个意思:

blob = 文件内容本体

tree = 文件夹清单(目录结构图,一层文件夹就一个 tree)

commit = 打包一整套目录结构(根 tree)+ 提交备注、作者信息


3. 安装与配置

3.1 安装 Git

Windows:下载 git-scm.com 的安装程序

macOS

1
2
3
4
5
# Homebrew
brew install git

# 或安装 Xcode Command Line Tools
xcode-select --install

**Linux (Debian/Ubuntu)**:

1
sudo apt-get install git

**Linux (CentOS/RHEL)**:

1
sudo yum install git

3.2 初始配置

安装完成后需要配置用户信息,这些信息会出现在每次提交中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 配置用户名
git config --global user.name "你的名字"

# 配置邮箱
git config --global user.email "your.email@example.com"

# 配置默认编辑器(可选)
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "vim" # Vim

# 配置默认分支名(Git 2.28+),未配置默认分支名是 master,可以通过以下命令修改
git config --global init.defaultBranch main

# 查看所有配置
git config --list

# 查看特定配置
git config user.name

配置的三个级别

image-20260616141519738


4. 创建仓库

4.1 初始化新仓库

1
2
3
4
5
6
# 创建一个新目录并初始化
mkdir my-project
cd my-project
git init

# 输出: Initialized empty Git repository in /path/to/my-project/.git/

初始化后,Git 会在项目根目录创建一个 .git 隐藏文件夹,包含所有版本控制所需的数据:

1
2
3
4
5
6
7
8
9
10
my-project/
├── .git/ ← Git 仓库的核心,所有数据都在这里
│ ├── HEAD ← 指向当前分支
│ ├── config ← 仓库配置
│ ├── objects/ ← 存储所有对象(blob, tree, commit, tag)
│ ├── refs/ ← 存储分支和标签的引用
│ │ ├── heads/ ← 分支引用
│ │ └── tags/ ← 标签引用
│ └── index ← 暂存区信息
└── (你的项目文件)

image-20260616142226628

4.2 克隆已有仓库

1
2
3
4
5
6
7
8
# 克隆远程仓库到本地
git clone https://github.com/user/repo.git

# 克隆到指定目录名
git clone https://github.com/user/repo.git my-folder

# 克隆指定分支
git clone -b develop https://github.com/user/repo.git

5. 工作流基础

5.1 文件生命周期

image-20260616143043300

注:git checkout 在旧版本中同时负责切换分支和恢复文件,
Git 2.23+ 引入了 git switch(切换分支)和 git restore(恢复文件),语义更清晰。

5.2 检查状态

1
2
3
4
5
6
7
8
9
10
11
12
git status

# 简短模式
git status -s
git status --short

# 示例输出:
# M index.html ← 已修改但未暂存(红色 M)
# M style.css ← 已暂存(绿色 M)
# ?? newfile.js ← 未跟踪文件
# A added.txt ← 新添加到暂存区
# D deleted.txt ← 已删除并暂存

5.3 添加文件到暂存区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加单个文件
git add index.html

# 添加多个文件
git add index.html style.css app.js

# 添加整个目录
git add src/

# 添加当前目录所有文件(包括子目录)
git add .

# 交互式暂存(可以只暂存文件的某一部分)
git add -p
git add --patch

# 只添加已跟踪且已修改的文件(不包括新文件)
git add -u

git add --patch 交互模式示例

1
2
3
4
5
6
7
8
9
10
11
$ git add -p index.html

diff --git a/index.html b/index.html
@@ -10,6 +10,10 @@
<body>
<h1>Hello World</h1>
+ <p>New paragraph</p>
+ <p>Another paragraph</p>
</body>

Stage this hunk [y,n,q,a,d,e,?]? _

5.4 提交更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 提交并指定消息
git commit -m "添加登录页面"

# 提交时添加详细说明(编辑器打开)
git commit

# 编辑器中的格式:
# 第一行:简短描述(50字以内)
# 空行
# 详细描述(72字以内换行)

# 跳过暂存区直接提交(不推荐)
git commit -a -m "修复了按钮样式"

# 修改上一次提交(未推送前)
git commit --amend -m "新的提交信息"

好的提交信息规范

1
2
3
4
5
6
7
8
feat: 添加用户登录功能
← 类型前缀
fix: 修复导航栏在小屏幕下的显示问题
docs: 更新 API 文档
style: 调整代码缩进和格式
refactor: 重构用户认证模块
test: 添加登录页面的单元测试
chore: 更新依赖版本

5.5 移除文件

1
2
3
4
5
6
7
8
9
10
11
12
# 从工作目录和暂存区中删除
git rm file.txt
# 等同于: rm file.txt && git add file.txt

# 只从暂存区移除,保留工作目录文件
git rm --cached file.txt

# 强制删除(即使文件已修改):本地物理删除文件 + 同步把删除操作加入暂存区
git rm -f file.txt

# 删除目录
git rm -r directory/

5.6 移动/重命名文件

1
2
3
4
5
6
# 重命名文件:重命名变更直接进入暂存,不用手动 git add
git mv oldname.txt newname.txt
# 等同于:
# mv oldname.txt newname.txt
# git rm oldname.txt
# git add newname.txt

6. 查看历史

6.1 git log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 查看提交历史
git log

# 简洁的一行显示
git log --oneline

# 显示最近的 5 条
git log -5

# 图形化显示分支历史
git log --graph --oneline --all --decorate

# 显示每次提交的差异
git log -p
git log --patch

# 显示文件级别的统计
git log --stat

# 自定义格式
git log --pretty=format:"%h - %an, %ar : %s"
# 占位符:
# %h = 简写哈希
# %an = 作者名
# %ar = 相对日期
# %s = 提交标题
# %ad = 作者日期

实用别名配置(给 Git 设置一个全局别名 lg,以后输入 git lg 等价于一长串彩色图形化提交日志,清晰看分支、提交时间、作者):

1
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

效果类似:

1
2
* a1b2c3d - (HEAD -> main) 添加登录页面 (2 days ago) <张三>
* e5f6g7h - 初始化项目 (3 days ago) <张三>

6.2 git diff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看工作目录与暂存区的差异
git diff

# 查看暂存区与上次提交的差异
git diff --staged
git diff --cached

# 查看工作目录与上次提交的差异
git diff HEAD

# 查看两个提交之间的差异
git diff commit1 commit2
git diff main develop

# 查看特定文件的差异
git diff -- index.html

# 查看更改的统计信息
git diff --stat

# 逐词比较(而非逐行)
git diff --word-diff

diff 输出解读

1
2
3
4
5
6
7
8
9
10
diff --git a/app.js b/app.js
index 83db48f..bf5e4cd 100644
--- a/app.js ← 旧文件
+++ b/app.js ← 新文件
@@ -1,4 +1,5 @@ ← -1,4 (旧文件第1行起共4行), +1,5 (新文件第1行起共5行)
function greet() {
- console.log("Hello"); ← 删除的行(红色)
+ const name = "World"; ← 新增的行(绿色)
+ console.log("Hello, " + name);
}

6.3 查看某次提交

1
2
3
4
5
6
7
8
9
10
11
# 查看某次提交的详细信息
git show a1b2c3d

# 只查看某次提交的文件列表
git show --stat a1b2c3d

# 查看某次提交中特定文件的内容
git show a1b2c3d:path/to/file.js

# 查看某个文件在特定提交时的内容
git show HEAD~2:index.html

6.4 追溯文件修改历史

1
2
3
4
5
6
7
8
9
10
11
# 查看文件的每一行是谁、在什么时候修改的
git blame index.html

# 只显示指定行范围
git blame -L 10,30 index.html

# 忽略空白变化
git blame -w index.html

# 显示更详细的信息
git blame -L 10,30 -s --date=short index.html

7. 撤销操作

7.1 撤销场景总览

image-20260616145001803

git restore --staged:仓库覆盖暂存区(把文件从待提交列表移出,修改依旧留在本地,只是取消暂存)

git restore:暂存区覆盖工作目录,撤销本地修改(彻底删掉你还没 add 的所有修改,文件变回上次 add 后的样子)

reset 三种模式区别:

  • --soft:只移动分支指针,修改保存在暂存区
  • --mixed(默认):指针回退,暂存清空,修改放回工作目录
  • --hard:仓库版本覆盖暂存 + 工作目录,未提交改动全部清除,不可恢复

7.2 撤销工作目录的修改

1
2
3
4
5
6
7
8
9
# 丢弃工作目录中对某个文件的修改(恢复到暂存区或 HEAD 的状态)
git restore index.html
# 旧命令: git checkout -- index.html

# 丢弃所有修改
git restore .

# 还原文件到某次提交的状态(例子:拿往前倒数第 2 次提交里的 index.html 文件版本,覆盖到你本地工作目录)
git restore --source=HEAD~2 index.html

7.3 撤销暂存区的文件

1
2
3
4
5
6
# 将文件从暂存区移出(取消暂存)
git restore --staged index.html
# 旧命令: git reset HEAD index.html

# 取消所有暂存
git restore --staged .

7.4 git reset 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# --soft: 只移动 HEAD,保留暂存区和工作目录
git reset --soft HEAD~1
# 场景:撤销最近一次提交,但保留所有修改在暂存区中

# --mixed (默认): 移动 HEAD,重置暂存区,保留工作目录
git reset HEAD~1
git reset --mixed HEAD~1
# 场景:撤销最近一次提交和暂存,但保留文件修改

# --hard: 移动 HEAD,重置暂存区和工作目录(危险!)
git reset --hard HEAD~1
# 场景:彻底放弃最近一次提交的所有更改

# 回退到特定提交
git reset --hard a1b2c3d

HEAD~n 语法

1
2
3
4
5
HEAD     = 当前提交
HEAD~1 = 上一次提交(父提交)
HEAD~2 = 上上次提交(祖父提交)
HEAD^ = 同上(HEAD~1)
HEAD^^ = 上上次提交

7.5 git revert

reset 不同,revert 通过创建一个新的提交来撤销更改,不会改变历史:

1
2
3
4
5
6
7
8
# 撤销某次提交的更改(创建新的提交)
git revert a1b2c3d

# 撤销最近一次提交
git revert HEAD

# 撤销但不自动提交(可以手动修改后再提交)
git revert --no-commit HEAD

reset vs revert

1
2
3
4
5
6
7
reset(历史被改写):          revert(历史保留):

○ ← ○ ← ○ ← ○ ○ ← ○ ← ○ ← ○ ← ●
↑ 撤销提交(新提交)
HEAD 移到这里

适用场景:本地提交,未推送 适用场景:已推送到远程的提交

8. 分支与合并

8.1 什么是分支

分支就是一个指向某次提交的可移动指针

1
2
3
4
5
6
                      feature-login(分支)

○ ← ○ ← ○ ← ○

main(分支)
HEAD(你当前所在)

8.2 创建和切换分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建分支
git branch feature-login

# 切换分支
git checkout feature-login
# 新命令: git switch feature-login

# 创建并切换到新分支
git checkout -b feature-login
# 新命令: git switch -c feature-login

# 查看所有分支
git branch # 本地分支
git branch -r # 远程分支
git branch -a # 所有分支

# 查看分支详情(最后一次提交)
git branch -v
git branch -vv # 同时显示远程跟踪分支

8.3 合并分支

1
2
# 将 feature-login 合并到当前分支
git merge feature-login

快进合并 (Fast-forward)

当目标分支没有新提交时,Git 只是移动指针:

1
2
3
4
5
6
7
合并前:                       合并后:

main main, feature
↓ ↓
○ ← ○ ← ○ ← ● ○ ← ○ ← ○ ← ●

feature
1
2
# 即使可以快进也创建合并提交
git merge --no-ff feature-login

三方合并 (Three-way merge)

当两个分支都有新提交时,Git 会创建一个新的合并提交:

1
2
3
4
5
合并前:                        合并后:

○ ← ● (feature) ○ ← ● ← ╮
/ main → / \
○ ← ○ ← ● (main) ○ ← ○ ← ● ← ──────── ★ (合并提交)

8.4 合并冲突

当两个分支修改了同一文件的同一部分时,就会产生冲突:

1
2
3
4
5
$ git merge feature-login

Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

冲突文件的内容:

1
2
3
4
5
<<<<<<< HEAD
<h1>Welcome to Our Site</h1>
=======
<h1>欢迎来到我们的网站</h1>
>>>>>>> feature-login

解决冲突的步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 查看哪些文件有冲突
git status

# 2. 编辑冲突文件,删除冲突标记,保留想要的代码
# 变成:
<h1>欢迎来到我们的网站</h1>

# 3. 标记为已解决
git add index.html

# 4. 完成合并提交
git commit -m "合并 feature-login 分支,解决冲突"

# 或者放弃合并
git merge --abort

8.5 删除分支

1
2
3
4
5
6
7
8
# 删除已合并的本地分支:Git 会做校验:feature-login 的所有提交已经全部合并到当前所在分支(比如 main),分支没有遗留未合并的代码,才允许删除。
git branch -d feature-login

# 强制删除(即使未合并)
git branch -D feature-login

# 删除远程分支
git push origin --delete feature-login

8.6 分支策略示例

1
2
3
4
5
6
7
8
9
10
11
main 分支(稳定,用于发布)

├── develop(开发分支)
│ │
│ ├── feature/user-login(功能分支)
│ ├── feature/payment (功能分支)
│ └── feature/search (功能分支)

├── release/v1.0(发布分支)

└── hotfix/critical-bug(热修复分支)

9. 远程仓库

9.1 远程仓库概念

远程仓库是托管在互联网或网络上的项目版本库。

image-20260616160658882

9.2 管理远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看远程仓库
git remote
git remote -v # 显示 URL

# 添加远程仓库(建立本地仓库 ↔ 云端仓库 的整体绑定,给远程仓库取别名origin)
git remote add origin https://github.com/user/repo.git

# 修改远程仓库 URL
git remote set-url origin https://github.com/user/new-repo.git

# 删除远程仓库
git remote remove origin

# 查看远程仓库详细信息
git remote show origin

9.3 拉取与推送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 推送本地分支到远程
git push origin main

# 推送并设置上游分支(之后只需 git push)
git push -u origin main
git push --set-upstream origin main

# 拉取远程更改并合并(= fetch + merge)
git pull origin main

# 拉取远程更改并变基(= fetch + rebase)
git pull --rebase origin main

# 只拉取不合并
git fetch origin

# 拉取所有远程仓库(当前本地项目中,你手动绑定过的全部云端项目别名)
# 举例:你本地这个项目绑了 2 个云端仓库
# origin:你的 Gitee 仓库(完整项目 A)
# upstream:原作者 GitHub 仓库(完整项目 B)
git fetch --all

# 查看远程分支
git branch -r

fetch vs pull

1
2
3
4
5
6
7
8
9
git fetch(安全,只下载不合并):
远程: ○ ← ○ ← ○ ← ●

origin/main 更新(仅更新远程跟踪分支)
本地: ○ ← ○ ← ● (main)
你的 main 不受影响,你可以手动决定何时合并

git pull(= fetch + merge,直接合并):
自动执行 fetch + merge,本地 main 直接更新

fetch + merge(pull) vs fetch + rebase:

操作前:

1
2
远程 origin/main:A ← B ← E
本地 main: A ← B ← C ← D

fetch + merge 效果

1
2
3
4
远程 origin/main:A ← B ← E
本地 main: A ← B ← E

← C ← D ← M

特点:历史分叉,多出一条合并提交 M,原有 C/D 提交哈希不变

fetch + rebase 效果

1
2
远程 origin/main:A ← B ← E
本地 main: A ← B ← E ← C' ← D'

特点:无分叉、无合并提交,C、D 被重生成全新提交 C’、D’,提交哈希改变,历史是一条直线

每次将代码提交到远程前需要先拉取一下解决冲突后再提交,具体是使用merge还是rebase看项目需要。

本地个人分支拉最新代码:用 rebase(保持历史干净)

功能分支合并到公共主干 main/develop:用 merge(团队安全、可追溯)

9.4 推送到远程的完整示例

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 初始化本地仓库
git init
git add .
git commit -m "初始提交"

# 2. 在 GitHub 上创建空仓库(不要勾选 README)

# 3. 关联远程仓库
git remote add origin https://github.com/your-username/your-repo.git

# 4. 推送(首次推送通常需要 -u,-u = 记住本地分支和远程分支的配对)
git push -u origin main

10. 储藏 (Stash)

暂存当前工作进度,让你可以切换到其他工作而不必提交:

1
2
3
4
5
6
7
8
9
正常工作到一半,突然需要切换到其他分支修复 bug:

git stash ──→ [工作区和暂存区被"藏起来"]
|
| git checkout hotfix
| 修复 bug...
| git checkout feature
|
git stash pop ←── 恢复之前的工作状态

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 储藏当前修改(包括已暂存的文件)
git stash

# 储藏时添加描述信息
git stash push -m "完成了一半的登录功能"

# 储藏时包含未跟踪的文件
git stash -u
git stash --include-untracked

# 查看储藏列表
git stash list
# 输出:
# stash@{0}: On feature-login: 完成了一半的登录功能
# stash@{1}: WIP on main: a1b2c3d 修复导航栏
# stash@{2}: On develop: 重构数据层

# 恢复最近一次储藏(不删除 stash)
git stash apply
git stash apply stash@{1} # 恢复指定的 stash

# 恢复并删除 stash
git stash pop
git stash pop stash@{1}

# 删除储藏
git stash drop stash@{0}

# 清空所有储藏
git stash clear

# 从储藏创建分支(以当前所在分支的当前提交为基底,新建分支并自动切换过去,取出 stash@{0} 里暂存的所有代码改动,恢复到新分支,如果储藏应用成功,自动删除这条储藏记录 stash@{0})
git stash branch new-branch-name stash@{0}

11. 标签 (Tag)

标签用于标记特定版本(如发布版本 v1.0.0):

11.1 两种标签

image-20260616165218834

11.2 常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建轻量标签
git tag v1.0.0

# 创建附注标签(推荐)
git tag -a v1.0.0 -m "版本 1.0.0 正式发布"

# 为之前的提交打标签
git tag -a v0.9.0 a1b2c3d -m "版本 0.9.0"

# 查看标签列表
git tag
git tag -l "v1.*" # 筛选标签

# 查看标签详细信息
git show v1.0.0

# 推送标签到远程
git push origin v1.0.0 # 推送单个标签
git push origin --tags # 推送所有标签

# 删除标签
git tag -d v1.0.0 # 删除本地标签
git push origin --delete v1.0.0 # 删除远程标签

# 检出标签(进入"分离头指针"状态,其实就是临时切换到标签v1.0.0指向的commit的版本,如需要基于标签版本修改,建议先基于标签新建分支,再修改提交)
git checkout v1.0.0

# 从标签创建分支
git checkout -b release-1.0 v1.0.0

12. 变基 (Rebase)

变基(Rebase)是将一系列提交按照原有次序应用到另一个分支上,产生更整洁的提交历史。

12.1 rebase vs merge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
场景:feature 分支从 main 的 C2 分出,main 已经推进到 C4

原始状态:
C3 ← C4 (main)
/
C1 ← C2
\
C5 ← C6 (feature)

git merge main(在 feature 上):
C3 ← C4 ← ╮
/ \
C1 ← C2 ★ (合并提交)
\ /
C5 ← C6 ← ╯

git rebase main(在 feature 上):
C1 ← C2 ← C3 ← C4 (main)
\
C5' ← C6' (feature,提交被重新应用)

12.2 交互式变基 (Interactive Rebase)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 变基到另一个分支(把你当前所在分支的所有提交,从原来分叉的位置拆下来,粘贴到 main 分支最新提交的后面,让提交历史变成一条直线)
git rebase main

# 交互式变基最近 3 次提交
git rebase -i HEAD~3

# 编辑器打开:
# pick a1b2c3d 添加登录页面
# pick e5f6g7h 修复验证bug
# pick i9j0k1l 调整样式

# 可用命令:
# pick = 保留该提交
# reword = 保留但修改提交信息
# edit = 保留但暂停以修改文件
# squash = 合并到上一个提交,保留信息
# fixup = 合并到上一个提交,丢弃信息
# drop = 删除该提交

squash 示例——将多个小提交合并为一个:

1
2
3
4
5
6
7
8
9
10
编辑前:
pick a1b2c3d 添加登录功能
squash b2c3d4e 修复登录页面的一个 typo
squash c3d4e5f 调整登录按钮颜色
pick d4e5f6g 添加注册功能

编辑后保存,Git 会要求你提供合并后的提交信息。
最终历史:
* d4e5f6g 添加注册功能
* a1b2c3d 添加登录功能 (包含了 typo 修复和按钮颜色调整)

12.3 变基冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 变基过程中遇到冲突
git rebase main

# 1. 解决冲突文件
# 2. 标记为已解决
git add conflicted-file.js

# 3. 继续变基
git rebase --continue

# 跳过当前提交(直接丢掉这次修改,跳过去应用下一个提交)
git rebase --skip

# 放弃整个变基
git rebase --abort

12.4 黄金法则

1
2
3
4
5
6
7
8
9
10
⚠️ 不要在公共分支(已推送到远程且有其他人基于此工作的分支)上使用 rebase!

正确使用场景:
✅ 整理本地尚未推送的提交
✅ 在 feature 分支上 rebase main,保持与主线同步
✅ squash 多个小提交为一个有意义的提交

错误使用场景:
❌ rebase 已推送到共享仓库的分支
❌ rebase main 分支上的提交

13. 拣选 (Cherry-pick)

将某个提交单独应用到当前分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 拣选单个提交
git cherry-pick a1b2c3d

# 拣选多个提交
git cherry-pick a1b2c3d e5f6g7h

# 拣选一段连续的提交(不包含 start)
git cherry-pick start..end

# 拣选但不自动提交(可以在提交前修改)
git cherry-pick --no-commit a1b2c3d

# 遇到冲突时:
git cherry-pick --continue # 解决后继续
git cherry-pick --skip # 跳过该提交
git cherry-pick --abort # 放弃

使用场景

1
2
3
4
5
main: ○ ← ○ ← ○ ← ●(修复了关键bug)

cherry-pick

release/v1.0: ○ ← ○ ← ○ ← ★(同样的bug修复,不影响main上的其他更改)

14. 二分查找 (Bisect)

场景:

现在代码有 Bug,但项目已经几十上百次提交,你根本不知道是哪一次改错导致的。

一个个手动回退测试太慢,bisect 用二分法不断折半缩小范围,最快几次就能锁定出问题的那一个提交。

用二分查找法定位引入 bug 的提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 开始二分查找
git bisect start

# 标记当前版本有问题
git bisect bad
# 或: git bisect bad HEAD

# 标记一个已知正常的版本
git bisect good v1.0.0
# 或: git bisect good a1b2c3d

# Git 会自动检出一个中间的版本让你测试
# 如果测试有问题:
git bisect bad

# 如果测试没问题:
git bisect good

# 重复以上步骤,直到找到引入 bug 的提交
# Git 会显示: a1b2c3d is the first bad commit

# 结束二分查找,回到原来的分支
git bisect reset

自动化 bisect

1
2
3
4
5
6
# 使用脚本自动测试
git bisect start HEAD v1.0.0
git bisect run npm test

# Git 会自动遍历提交,每次运行 npm test
# 直到找到第一个失败的提交

15. Git 工作流

15.1 集中式工作流

最简单的模式,所有人直接在 main 分支上工作:

1
2
3
main: ○ ← ○ ← ○ ← ● ← ● ← ●
↑ ↑
张三 李四

适用场景:个人项目或小型团队,但容易产生冲突。

15.2 功能分支工作流 (GitHub Flow)

每个新功能/修复在单独的分支上开发,完成后通过 Pull Request 合并:

1
2
3
4
5
main:
○ ← ○ ← ○ ← ────── ★(合并PR) ← ────── ★(合并PR)
\ / \ /
○ ← ○ ← ○ ○ ← ○ ← ○
张三的功能 李四的功能

步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 从 main 创建功能分支
git checkout -b feature/my-feature main

# 2. 开发并提交
git add .
git commit -m "实现了新功能"

# 3. 推送分支(即使未完成也可以推送以备份/协作)
git push -u origin feature/my-feature

# 4. 在 GitHub 上创建 Pull Request
# 5. 代码审查通过后合并
# 6. 删除功能分支
git branch -d feature/my-feature
git push origin --delete feature/my-feature

15.3 Git Flow

更严格的分支模型,适合有明确发布周期的项目:

image-20260616174529854

  • main:生产就绪的代码,每次提交都是一个发布版本
  • develop:集成分支,最新的开发成果
  • **feature/***:从 develop 分出,完成后合并回 develop
  • **release/***:从 develop 分出,准备发布,修 bug 后合并到 main 和 develop
  • **hotfix/***:从 main 分出,紧急修复,合并到 main 和 develop

上面这套是标准 GitFlow 工作流,我按从上到下顺序,讲清每个分支怎么创建、从哪切、什么时候合并

先梳理层级关系:

  1. main:线上稳定发布分支,永远可上线
  2. develop:开发主分支,所有新功能汇总于此
  3. feature/*:单独需求功能分支,从 develop 分出
  4. release/v1.0:版本预发布分支,从 develop 分出
  5. hotfix/*:线上紧急 bug 修复分支,从 main 分出

一、develop 开发分支(初始化只做一次)

首次仓库只有 main,创建 develop:

1
2
3
4
5
6
# 切到main
git checkout main
# 新建并切换到develop
git checkout -b develop
# 推送到远程
git push -u origin develop

二、feature/xxx 功能分支(做新需求时创建)

创建规则:从 develop 分支切出来

1
2
3
4
5
6
7
8
9
# 1. 先切回开发主分支,保证代码最新
git checkout develop
git pull origin develop

# 2. 创建功能分支 user-login
git checkout -b feature/user-login

# 写完代码、提交后,推送远程
git push -u origin feature/user-login

功能完成后合并流程

  1. 提 PR/MR 合并进 develop
  2. 合并完成,删除本地 + 远程 feature 分支
1
2
3
4
# 删本地
git branch -d feature/user-login
# 删远程
git push origin --delete feature/user-login

三、release/v1.0 版本发布分支(开发完毕准备上线)

创建规则:从 develop 分出

当 develop 上所有本次迭代功能都开发完成,准备打包上线时新建 release 分支:

1
2
3
4
5
6
7
# 1. 更新develop
git checkout develop
git pull

# 2. 创建发布分支
git checkout -b release/v1.0
git push -u origin release/v1.0

release 分支作用

只改版本号、修复小 bug,不新增大功能;测试修复完后:

  1. 合并到 main(打上版本标签,正式上线)
  2. 同时合并回 develop(把修复的 bug 同步给开发分支)
  3. 双分支合并完成后,删除 release 分支

四、hotfix/critical-bug 热修复分支(线上紧急故障)

创建规则:从 main 稳定分支切出来

线上 main 出现严重 bug,不能等下一轮迭代,立刻从线上 main 拉出修复分支:

1
2
3
4
5
6
7
# 1. 切换到线上稳定分支main
git checkout main
git pull origin main

# 2. 创建热修复分支
git checkout -b hotfix/critical-bug
git push -u origin hotfix/critical-bug

bug 修复完成后合并流程

  1. 修复代码提交,合并进 main,重新上线并打版本 tag
  2. 必须同步合并进 develop,避免下一轮开发又出现这个 bug
  3. 两边全部合并完成后,删除 hotfix 分支

核心区分总结

分支类型 从哪个分支创建 合并目标分支 使用场景
feature/xxx develop develop 日常新功能开发
release/vx.x develop main、develop 迭代版本整体测试、发布
hotfix/xxx main main、develop 线上紧急 bug 快速修复
main 初始仓库 仅接收 release/hotfix 合并 线上稳定代码,生产环境
develop main(仅一次) 汇总所有 feature、接收 release/hotfix 修复 日常开发汇总

15.4 分支策略比较

工作流 分支数 复杂度 适用场景
集中式 1 个人项目
GitHub Flow 2+ 持续部署的 Web 项目
Git Flow 5+ 有明确版本发布周期的项目
GitLab Flow 3+ 需要环境分支(staging/production)的项目

16. 常用命令速查表

16.1 基础操作

命令 说明
git init 初始化仓库
git clone <url> 克隆仓库
git status 查看状态
git add <file> 暂存文件
git add . 暂存所有文件
git commit -m "msg" 提交
git commit --amend 修改上次提交
git log 查看历史
git diff 查看差异

16.2 分支操作

命令 说明
git branch 查看分支
git branch <name> 创建分支
git switch <name> 切换分支
git switch -c <name> 创建并切换
git merge <branch> 合并分支
git branch -d <name> 删除分支
git branch -D <name> 强制删除分支

16.3 远程操作

命令 说明
git remote -v 查看远程仓库
git remote add <name> <url> 添加远程仓库
git fetch 拉取数据
git pull 拉取并合并
git push 推送
git push -u origin main 推送并设置上游
git push origin --delete <branch> 删除远程分支

16.4 撤销操作

命令 说明
git restore <file> 撤销工作区修改
git restore --staged <file> 取消暂存
git reset --soft HEAD~1 撤销提交,保留暂存
git reset --mixed HEAD~1 撤销提交和暂存
git reset --hard HEAD~1 完全撤销提交
git revert <commit> 安全撤销(创建新提交)

16.5 高级操作

命令 说明
git stash 储藏修改
git stash pop 恢复储藏
git rebase <branch> 变基
git rebase -i HEAD~3 交互式变基
git cherry-pick <commit> 拣选提交
git tag -a v1.0 -m "msg" 创建附注标签
git bisect start 二分查找
git blame <file> 查看文件修改历史

16.6 .gitignore 常用模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 依赖
node_modules/
vendor/

# 构建产物
dist/
build/
*.exe
*.dll
*.so
*.dylib

# 日志
*.log
npm-debug.log*

# 环境配置
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# 系统文件
.DS_Store
Thumbs.db

# 测试覆盖率
coverage/

# 临时文件
*.tmp
*.temp

17.相关工具

我们也可以用一些git可视化工具或者直接在idea里操作git,下面是我看过的一些还行的教程:

git小乌龟使用教程:

git可视化工具小乌龟安装&使用_git小乌龟-CSDN博客

TortoiseGit-使用教程(图文详细版) - 吴川华仔 - 博客园

idea操作git:

Git在IDEA中的使用(详细图文全解)_idea操作git-CSDN博客

教程:在 IntelliJ IDEA 中开始使用 Git | IntelliJ IDEA 文档


附录:实战练习

练习 1:基本工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 创建项目
mkdir git-practice && cd git-practice
git init

# 2. 创建文件并提交
echo "# My Project" > README.md
git add README.md
git commit -m "初始化项目"

# 3. 修改文件
echo "Hello World" >> README.md
git status # 查看状态
git diff # 查看修改

# 4. 暂存并提交
git add README.md
git commit -m "添加 Hello World"

# 5. 查看历史
git log --oneline

练习 2:分支操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 创建并切换到新分支
git switch -c feature-greeting

# 2. 在新分支上提交
echo "function greet() { return 'Hi!'; }" > app.js
git add app.js
git commit -m "添加问候函数"

# 3. 切回 main 并做不同的修改
git switch main
echo "function add(a,b) { return a+b; }" > app.js
git add app.js
git commit -m "添加加法函数"

# 4. 合并 feature-greeting(会产生冲突)
git merge feature-greeting

# 5. 解决冲突后提交
# 编辑 app.js 保留两个函数
git add app.js
git commit -m "合并问候和加法功能"

练习 3:交互式变基

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 创建几个"脏"提交
echo "line 1" >> file.txt && git add . && git commit -m "WIP 1"
echo "line 2" >> file.txt && git add . && git commit -m "WIP 2"
echo "line 3" >> file.txt && git add . && git commit -m "WIP 3"
echo "line 4" >> file.txt && git add . && git commit -m "完成功能"

# 2. 使用交互式变基整理
git rebase -i HEAD~4
# 将 "WIP 1", "WIP 2", "WIP 3" 标记为 fixup
# "完成功能" 保持 pick

# 3. 查看整洁后的历史
git log --oneline
# 只剩一条: "完成功能"

提示:学习 Git 最好的方式就是多动手。建议创建一个测试仓库,把本文档中的命令都亲自执行一遍,观察每一步的输出和变化。