Git 从入门到精通:命令、参数、工作流与实战练习
适用人群:第一次接触 Git 的学习者,以及希望系统补齐分支、合并、变基、撤销、恢复与团队协作知识的开发者。
示例默认使用 Git 2.x,命令行环境为 macOS、Linux 或 Git Bash。
文中的<名称>表示需要替换的内容,[可选项]表示可以省略。
目录
- Git 到底是什么
- 安装与首次配置
- 必须理解的四个区域
- 创建和获取仓库
- 日常基本操作
- 查看历史与差异
- 分支详解
- 合并、冲突与变基
- 远程仓库协作
- 撤销、回退与恢复
- 标签与版本发布
- 暂存工作:stash
- 选择性移植:cherry-pick
- 查错与定位
- 子模块、工作树与大文件
- 配置、别名、钩子与忽略规则
- 团队工作流
- 综合练习案例
- 常见问题与危险命令
- 命令速查表与进阶路线
1. Git 到底是什么
Git 是一个分布式版本控制系统。它记录的核心不是“某个文件改了几行”,而是项目在一系列时间点上的快照。
1.1 Git、GitHub、GitLab 的区别
- Git:本地版本控制工具,不联网也能提交、建分支和查看历史。
- GitHub / GitLab / Gitee:托管 Git 仓库并提供代码评审、议题、CI/CD 等协作功能的平台。
- 远程仓库:服务器上的 Git 仓库副本;它不是 Git 的必需品,但团队协作通常需要它。
1.2 Git 的核心对象
- blob:文件内容。
- tree:目录结构及其包含的对象。
- commit:一次项目快照,含作者、时间、说明和父提交。
- tag:指向某个提交的固定标记。
- branch:指向某个提交、会随新提交向前移动的引用。
- HEAD:你当前所在位置,通常指向当前分支。
一个普通提交历史可理解为:
A <- B <- C main
^
HEAD
提交 C 记住自己的父提交 B;分支 main 指向 C;HEAD 指向 main。
2. 安装与首次配置
2.1 检查版本
git --version
git --help
git help <命令>
git <命令> -h
-
git help <命令>:打开完整手册,如git help commit。 -
git <命令> -h:显示简短参数帮助。
2.2 配置身份
git config --global user.name "Zhang San"
git config --global user.email "zhangsan@example.com"
常见配置层级:
| 层级 | 参数 | 作用范围 |
|---|---|---|
| 系统 | --system |
当前电脑全部用户 |
| 用户 | --global |
当前用户全部仓库 |
| 仓库 | --local |
当前仓库,默认层级 |
| 临时 | -c key=value |
仅本次命令 |
后一级会覆盖前一级。查看配置及来源:
git config --list --show-origin
git config --global --get user.name
git config --local user.email "work@example.com"
git config --global --unset <键>
git config --global --edit
推荐配置:
git config --global init.defaultBranch main
git config --global core.editor "code --wait"
git config --global pull.rebase false
git config --global fetch.prune true
git config --global color.ui auto
pull.rebase是团队策略。设为false表示拉取时默认合并;团队采用线性历史时可设为true。不确定时不要盲目全局修改。
练习 1:建立个人配置
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"
git config --list --show-origin
目标:能指出每条配置来自哪个文件,并理解仓库级配置为什么能覆盖全局配置。
3. 必须理解的四个区域
工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程仓库
^ |
|---- restore -------|
- 工作区(working tree):实际看到和编辑的文件。
- 暂存区(index/staging area):下一次提交准备包含的快照。
-
本地仓库(repository):
.git中保存的提交和引用。 - 远程仓库(remote repository):GitHub 等服务器上的仓库。
一个文件还可能处于以下状态:
-
untracked:未跟踪,新文件尚未加入 Git。 -
tracked:已跟踪。 -
modified:工作区内容相对暂存区有变化。 -
staged:变化已经放入暂存区。 -
committed:变化已经进入本地提交。
git status 是迷路时的第一条命令。
4. 创建和获取仓库
4.1 git init:初始化仓库
mkdir demo && cd demo
git init
git init --initial-branch=main
git init --bare server.git
重要参数:
-
-b, --initial-branch=<名称>:指定初始分支。 -
--bare:创建裸仓库,没有工作区,常用于服务器中转仓库。 -
--shared[=<权限>]:允许用户组共享仓库。 -
--separate-git-dir=<路径>:让工作区和 Git 元数据分开存放。
重复运行 git init 一般不会丢失历史,只会重新初始化必要文件。
4.2 git clone:克隆仓库
git clone https://example.com/team/app.git
git clone https://example.com/team/app.git my-app
git clone --branch develop --single-branch <URL>
git clone --depth 1 <URL>
git clone --filter=blob:none <URL>
重要参数:
-
-b, --branch <名称>:克隆后检出指定分支或标签。 -
--single-branch:只获取一个分支的历史。 -
--depth <n>:浅克隆,只取最近 n 层历史。 -
--filter=blob:none:部分克隆,按需下载文件内容,适合超大仓库。 -
--recurse-submodules:同时初始化并拉取子模块。 -
--origin <名称>:将默认远程名从origin改成指定名称。 -
--bare:克隆成裸仓库。 -
--mirror:镜像全部引用,通常用于备份迁移,比--bare更完整。
浅克隆节省时间和空间,但历史分析、变基和某些 CI 工具可能受限。可用
git fetch --unshallow补全历史。
5. 日常基本操作
5.1 git status:查看状态
git status
git status --short
git status --branch --short
git status --ignored
-
-s, --short:紧凑输出;两列状态分别代表暂存区和工作区。 -
-b, --branch:显示分支及领先/落后情况。 -
--ignored:同时显示被忽略文件。 -
--porcelain:机器可稳定解析的格式。
短格式示例:
M app.js # 工作区修改,未暂存
M api.js # 已暂存
MM both.js # 已暂存后又继续修改
?? note.md # 未跟踪
5.2 git add:放入暂存区
git add README.md
git add src/
git add .
git add -A
git add -u
git add -p
git add -N new-file.txt
重要参数:
-
.:加入当前目录及子目录的变化。 -
-A, --all:加入整个仓库的新增、修改和删除。 -
-u, --update:只加入已跟踪文件的修改和删除,不加入新文件。 -
-p, --patch:按代码块交互式暂存,适合拆分提交。 -
-i, --interactive:交互式选择。 -
-N, --intent-to-add:标记“准备添加”,便于在 diff 中看到完整新文件。 -
-f, --force:强行加入被.gitignore忽略的文件,慎用。
git add 不是“添加文件名”,而是把当前内容快照复制到暂存区。暂存后继续编辑,需再次 add 才会纳入提交。
5.3 git commit:创建提交
git commit -m "feat: add login page"
git commit -am "fix: validate email"
git commit --amend
git commit --amend --no-edit
git commit --allow-empty -m "chore: trigger CI"
重要参数:
-
-m <信息>:直接写提交说明,可重复使用形成多段说明。 -
-a, --all:自动暂存已跟踪文件的修改和删除;不会加入新文件。 -
--amend:替换最近一次提交,可修改内容或说明。 -
--no-edit:沿用原提交说明,常和--amend配合。 -
--author="姓名 <邮箱>":指定作者。 -
--date=<日期>:指定作者日期。 -
--no-verify:绕过提交钩子,除非明确知道原因,否则不要使用。 -
-S, --gpg-sign:对提交签名。 -
--allow-empty:允许没有文件变化的提交。 -
--fixup=<提交>:创建用于自动整理历史的修正提交。
推荐提交信息结构:
<类型>(可选范围): 简短说明
可选的详细原因、实现方式和影响
可选的关联事项
常见类型:feat、fix、docs、refactor、test、chore。
5.4 git rm 与 git mv
git rm old.txt
git rm -r old-dir/
git rm --cached secret.env
git mv old-name.md new-name.md
-
git rm:从工作区删除并把删除放入暂存区。 -
--cached:只停止跟踪,保留本地文件,常用于误提交的配置文件。 -
-f:强制删除有未暂存/未提交变化的文件,慎用。 -
git mv:移动或重命名并暂存;本质相当于移动后执行git add -A。
Git 不存储“重命名”对象,而是根据内容相似度在展示历史时推断。
练习 2:完成第一次提交
mkdir git-basic-lab && cd git-basic-lab
git init -b main
# 创建 README.md,并写入项目标题
git status
git add README.md
git status --short
git commit -m "docs: initialize README"
git log --oneline
继续修改 README,分别观察修改前、暂存后、再次修改后的 git status --short。
6. 查看历史与差异
6.1 git log:浏览提交历史
git log
git log --oneline --graph --decorate --all
git log -5
git log --author="Zhang"
git log --since="2026-01-01" --until="2026-02-01"
git log --grep="login"
git log -- src/auth.js
git log -p -- src/auth.js
git log -S"validateToken" --all
git log A..B
git log A...B
常用参数:
-
--oneline:一行一个提交。 -
--graph:绘制文本分支图。 -
--decorate:显示分支和标签。 -
--all:显示所有引用可达历史。 -
-p:显示每次提交的补丁。 -
--stat:显示文件及行数统计。 -
--name-only/--name-status:显示文件名 / 文件状态。 -
-n <数量>:限制条数。 -
--first-parent:只沿第一父提交查看,适合主分支发布史。 -
--follow -- <文件>:尝试跨重命名追踪单个文件。 -
-S<文本>:查找使某段文本出现次数改变的提交。 -
-G<正则>:查找补丁中匹配正则的提交。 -
--reverse:从旧到新显示。
范围语法:
-
A..B:B 可达但 A 不可达的提交,常理解为“B 比 A 多了什么”。 -
A...B:A、B 各自从共同祖先分叉后的提交。 -
^A B:排除 A 可达、包含 B 可达,与A..B类似。
推荐别名:
git config --global alias.lg "log --graph --decorate --oneline --all"
git lg
6.2 git show:查看对象或提交
git show HEAD
git show HEAD~2
git show <提交>:path/to/file
git show --stat <提交>
git show --name-only <提交>
-
HEAD~2:沿第一父提交向前两次。 -
HEAD^2:合并提交的第二个父提交。 -
<提交>:<路径>:查看某次提交中的文件内容。
6.3 git diff:比较差异
git diff # 工作区 vs 暂存区
git diff --staged # 暂存区 vs HEAD
git diff HEAD # 工作区+暂存区 vs HEAD
git diff <提交1> <提交2>
git diff <分支1>...<分支2>
git diff --stat
git diff --name-status
git diff --word-diff
git diff --check
关键参数:
-
--cached, --staged:查看准备提交的内容。 -
--stat:只看统计。 -
--name-only:只看文件名。 -
--name-status:看文件名和新增/修改/删除状态。 -
--word-diff:按词显示差异。 -
--check:检查空白错误。 -
--color-words:彩色词级差异。 -
-- <路径>:限定路径,避免路径被当成修订名。
6.4 git blame:逐行追溯
git blame src/app.js
git blame -L 20,40 src/app.js
git blame -w src/app.js
git blame -C src/app.js
-
-L <起始>,<结束>:限制行范围。 -
-w:忽略空白变化。 -
-C:检测从其他文件复制或移动的代码。
blame 用于了解上下文,不应用于“追责”;结合对应提交说明和评审记录判断原因。
7. 分支详解
7.1 git branch:创建、查看、删除分支
git branch
git branch --all
git branch --verbose --verbose
git branch feature/login
git branch feature/login <起点>
git branch -m new-name
git branch -d feature/login
git branch -D feature/login
git branch --merged
git branch --no-merged
重要参数:
-
-a, --all:本地和远程跟踪分支。 -
-r, --remotes:只看远程跟踪分支。 -
-v/-vv:显示最近提交;-vv还显示上游关系。 -
-m:重命名分支;-M强制重命名。 -
-d:仅删除已合并分支。 -
-D:强制删除,即使未合并。 -
--merged [提交]:列出已合入指定提交的分支。 -
--no-merged [提交]:列出尚未合入的分支。 -
--contains <提交>:列出包含某提交的分支。 -
-u, --set-upstream-to=<远程/分支>:设置当前分支的上游。
创建分支只是新建一个轻量指针,不会自动切换。
7.2 git switch:切换分支
git switch main
git switch -c feature/login
git switch -c hotfix v1.2.0
git switch -C temp <提交>
git switch -
git switch --detach <提交>
git switch --track origin/develop
-
-c:创建并切换,已有同名分支则失败。 -
-C:创建或重置并切换;可能移动已有分支,慎用。 -
-:返回上一个分支。 -
--detach:进入分离 HEAD,不依附分支。 -
--track:建立本地分支并跟踪远程分支。 -
--discard-changes:丢弃阻碍切换的本地变化,高风险。
7.3 git checkout:传统多用途命令
git checkout main
git checkout -b feature/login
git checkout -- file.txt
git checkout <提交> -- file.txt
历史上 checkout 同时负责切分支和恢复文件,容易混淆。新代码建议:
- 切换分支用
git switch。 - 恢复文件用
git restore。
7.4 分离 HEAD
执行 git switch --detach <提交> 后,HEAD 直接指向提交。此时可以试验和提交,但新提交没有分支名保护,之后可能被回收。若要保留:
git switch -c experiment
练习 3:功能分支
git switch -c feature/profile
# 修改 README 并提交
git add README.md
git commit -m "feat: add profile description"
git switch main
git log --oneline --graph --all
git switch feature/profile
目标:观察 main 没有移动,而 feature/profile 指向新提交。
8. 合并、冲突与变基
8.1 git merge:合并分支
先切到“接收变化”的分支:
git switch main
git merge feature/login
常见结果:
- Fast-forward:main 没有独立新提交,指针直接前移。
- 三方合并:双方都发展,创建一个有两个父提交的合并提交。
- 冲突:同一区域的变化无法自动统一,需要人工处理。
重要参数:
git merge --ff-only feature/login
git merge --no-ff feature/login
git merge --squash feature/login
git merge --no-commit feature/login
git merge --abort
git merge --continue
-
--ff-only:只允许快进,否则失败;适合强制线性历史。 -
--no-ff:即使可以快进也创建合并提交,保留功能分支边界。 -
--squash:把目标分支的综合变化放入暂存区,不保留其分支提交关系,之后需手动提交。 -
--no-commit:合并成功后先暂停,以便检查再提交;快进时不会暂停。 -
--abort:尽量回到合并前状态。 -
--continue:解决冲突并暂存后继续。 -
-X ours/-X theirs:冲突时偏好一侧,但不是无条件使用整侧内容,慎用。
8.2 解决冲突
冲突文件会出现:
<<<<<<< HEAD
当前分支内容
=======
被合并分支内容
>>>>>>> feature/login
标准步骤:
git status
# 编辑冲突文件,删除标记,保留正确结果
git add <已解决文件>
git merge --continue
# 若不想继续:git merge --abort
辅助命令:
git diff --name-only --diff-filter=U
git checkout --ours <文件>
git checkout --theirs <文件>
git mergetool
注意:在 rebase 冲突中,ours/theirs 的直觉可能与 merge 相反,务必查看内容,不要只凭名称选择。
8.3 git rebase:变基
假设功能分支从旧 main 分出:
C---D feature
/
A---B---E---F main
在 feature 上执行:
git switch feature
git rebase main
得到:
A---B---E---F main
\
C'---D' feature
C'、D' 是新提交,提交哈希变化了。常用参数:
git rebase main
git rebase --onto <新基底> <旧基底> <分支>
git rebase --continue
git rebase --skip
git rebase --abort
git rebase --autostash main
git rebase -i HEAD~5
git rebase -i --autosquash main
-
--onto:精确移动一段提交。 -
--continue:解决冲突并add后继续。 -
--skip:跳过当前补丁,可能丢掉该提交的变化。 -
--abort:恢复到 rebase 前。 -
--autostash:临时保存工作区变化,完成后恢复;仍可能冲突。 -
-i, --interactive:交互式整理历史。 -
--autosquash:自动排列fixup!/squash!提交。
交互式 rebase 指令:
| 指令 | 简写 | 用途 |
|---|---|---|
pick |
p |
保留提交 |
reword |
r |
修改提交说明 |
edit |
e |
暂停以修改提交 |
squash |
s |
合入前一个提交并编辑说明 |
fixup |
f |
合入前一个提交并丢弃本提交说明 |
drop |
d |
删除提交 |
break |
b |
在此暂停 |
黄金规则:不要随意 rebase 已被他人基于其开发的公共提交。 因为 rebase 改写提交身份,会迫使其他人处理重复历史。
8.4 merge 与 rebase 的选择
| 场景 | 建议 |
|---|---|
| 更新尚未共享的个人功能分支 |
rebase main,历史清晰 |
| 合入公共主分支并保留完整上下文 | merge --no-ff |
| 团队要求严格线性历史 | 更新时 rebase,合入时 fast-forward/squash |
| 不确定提交是否已被他人使用 | 优先 merge |
9. 远程仓库协作
9.1 git remote:管理远程地址
git remote -v
git remote add origin <URL>
git remote show origin
git remote get-url origin
git remote set-url origin <新URL>
git remote rename origin upstream
git remote remove origin
origin 只是默认名称,不是特殊关键字。Fork 工作流常见:
-
origin:自己的 Fork。 -
upstream:原项目仓库。
9.2 git fetch:只下载,不合并
git fetch origin
git fetch --all
git fetch --prune
git fetch origin main
git fetch --tags
git fetch --depth 100
git fetch --unshallow
-
--all:抓取所有远程。 -
-p, --prune:清理远端已删除的远程跟踪分支。 -
--tags:显式抓取全部标签。 -
--depth:调整浅克隆深度。 -
--unshallow:把浅克隆补为完整仓库。 -
--dry-run:预览。
origin/main 是本地记录的远程跟踪引用;只有 fetch 后才会更新。
9.3 git pull:抓取并整合
pull = fetch + merge/rebase:
git pull
git pull --ff-only
git pull --rebase
git pull --rebase=merges
git pull --no-rebase
-
--ff-only:只接受快进,避免意外产生合并提交。 -
--rebase:把本地提交变基到远程新提交之后。 -
--rebase=merges:尝试保留本地合并结构。 -
--no-rebase:使用合并。
新手推荐先拆开执行以看清过程:
git fetch origin
git log --oneline --graph --all
git merge origin/main # 或 git rebase origin/main
9.4 git push:上传引用和对象
git push -u origin feature/login
git push
git push origin main
git push --tags
git push origin v1.0.0
git push origin --delete feature/old
git push --force-with-lease
git push --dry-run
重要参数:
-
-u, --set-upstream:设置上游,以后可直接git push/git pull。 -
--all:推送所有本地分支。 -
--tags:推送全部标签;普通 push 不会自动推送所有标签。 -
--delete:删除远程分支或标签引用。 -
--force:无条件强推,可能覆盖他人工作,通常不要用。 -
--force-with-lease:仅当远端仍是自己预期的状态才强推;改写个人远程分支后首选。 -
--atomic:要求多个引用全部成功或全部失败,服务器需支持。 -
--dry-run:预览,不实际推送。
即使 --force-with-lease 更安全,推送前也应先 git fetch,并确认目标是个人分支而非受保护主分支。
9.5 上游分支
查看:
git branch -vv
设置或取消:
git branch --set-upstream-to=origin/main main
git branch --unset-upstream
练习 4:模拟远程协作
无需联网也可完成:
git init --bare remote.git
git clone remote.git alice
git clone remote.git bob
- Alice 创建文件、提交并推送 main。
- Bob 执行
git pull,修改另一个文件并推送。 - Alice 先制造一个本地提交,再抓取 Bob 的提交。
- 分别尝试 merge 和 rebase,画出两种历史图。
10. 撤销、回退与恢复
先判断变化位于哪里,再选择命令:
| 目标 | 推荐命令 | 是否改写历史 |
|---|---|---|
| 丢弃未暂存的文件修改 | git restore <文件> |
否 |
| 取消暂存,保留工作区修改 | git restore --staged <文件> |
否 |
| 修改最近一次未共享提交 | git commit --amend |
是 |
| 撤销已共享提交 | git revert <提交> |
否,新增反向提交 |
| 本地分支退回旧提交 | git reset |
可能 |
| 找回误删分支/提交 | git reflog |
否 |
10.1 git restore:恢复文件
git restore file.txt
git restore --staged file.txt
git restore --source=HEAD~2 file.txt
git restore --source=<提交> --staged --worktree file.txt
git restore -p file.txt
- 默认:从暂存区恢复工作区,丢弃未暂存修改。
-
--staged:从 HEAD 恢复暂存区,即取消暂存,默认保留工作区。 -
-s, --source=<来源>:指定恢复来源。 -
-W, --worktree:恢复工作区。 -
-S, --staged:恢复暂存区。 -
-p:按块选择。
git restore file.txt会覆盖未提交修改,执行前可先git diff或git stash。
10.2 git reset:移动分支并重置状态
三种主要模式:
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
| 模式 | 分支/HEAD | 暂存区 | 工作区 | 典型用途 |
|---|---|---|---|---|
--soft |
重置 | 保留 | 保留 | 撤销提交,重新组织提交 |
--mixed(默认) |
重置 | 重置 | 保留 | 撤销提交和暂存,保留修改 |
--hard |
重置 | 重置 | 重置 | 完全丢弃已跟踪文件变化 |
其他形式:
git reset HEAD file.txt # 传统取消暂存
git reset -p # 按块取消暂存
git reset --keep <提交> # 尽量保留本地修改
git reset --merge ORIG_HEAD # 常用于撤销合并/拉取
reset <文件> 不会移动分支,只操作暂存区;这与 reset <提交> 的行为不同。现代写法优先 git restore --staged <文件>。
10.3 git revert:安全撤销提交
git revert <提交>
git revert HEAD
git revert --no-commit A..B
git revert -m 1 <合并提交>
git revert --abort
git revert --continue
- 创建一个反向变化的新提交,保留既有历史,适合已推送提交。
-
-n, --no-commit:只把反向变化放入暂存区,可一次撤销多项后统一提交。 -
-m <父编号>:撤销合并提交时指定保留哪条主线;语义影响后续合并,必须理解后使用。
范围 A..B 不包含 A。撤销多个提交时要检查顺序和结果。
10.4 git reflog:找回“消失”的提交
git reflog
git reflog show main
git show HEAD@{3}
git switch -c rescue <提交哈希>
reflog 记录本地引用曾经指向哪里,可恢复误 reset、误删分支或 rebase 前的提交。它主要是本地恢复日志,会过期,不是永久备份。
恢复流程:
- 立即停止大量改写操作。
- 用
git reflog找到正确提交。 - 用
git show <哈希>验证内容。 - 用
git switch -c rescue <哈希>建保护分支。
10.5 git clean:清理未跟踪文件
git clean -n
git clean -nd
git clean -fd
git clean -fdX
git clean -fdx
-
-n, --dry-run:预览,强烈建议先执行。 -
-f, --force:实际删除。 -
-d:包括未跟踪目录。 -
-X:只删除被忽略文件。 -
-x:连被忽略文件也删除,风险极高。 -
-i:交互式清理。
未跟踪文件不一定能被 Git 恢复。执行 clean 前必须预览。
练习 5:恢复事故
- 连续创建三个提交 A、B、C。
- 执行
git reset --hard HEAD~2,让 B、C 从分支历史消失。 - 用
git reflog找到原 C。 - 创建
rescue分支恢复它。 - 回到 main,使用
git revert撤销一个已提交变化,比较它与 reset 的历史差异。
11. 标签与版本发布
11.1 git tag
git tag
git tag --list "v1.*"
git tag v1.0.0
git tag -a v1.0.0 -m "Release 1.0.0"
git tag -s v1.0.0 -m "Release 1.0.0"
git show v1.0.0
git tag -d v1.0.0
- 轻量标签:只是固定引用,
git tag v1.0.0。 - 附注标签:独立对象,包含打标签者、日期和说明,发布版本推荐。
-
-a:创建附注标签。 -
-s:创建签名标签。 -
-v:验证签名。 -
-f:强制移动已有标签;公开标签不应随意移动。
推送与删除远程标签:
git push origin v1.0.0
git push origin --tags
git push origin --delete v1.0.0
检出标签会进入分离 HEAD。如需修复旧版:
git switch -c hotfix/v1.0.1 v1.0.0
推荐遵循语义化版本:主版本.次版本.修订号,如 v2.3.1。
12. 暂存工作:stash
12.1 常用操作
git stash push -m "WIP: login form"
git stash push -u -m "include untracked"
git stash push -a
git stash push -p
git stash list
git stash show -p stash@{0}
git stash apply stash@{1}
git stash pop
git stash drop stash@{0}
git stash clear
git stash branch rescue-work stash@{0}
参数和区别:
-
push:创建 stash;推荐写说明。 -
-u, --include-untracked:包含未跟踪文件。 -
-a, --all:还包含被忽略文件。 -
-p, --patch:按块选择。 -
--keep-index:保留暂存区内容在当前工作树中。 -
apply:应用但保留 stash 记录。 -
pop:应用并在成功时删除记录。 -
branch:从 stash 原基点建分支并应用,处理旧 stash 冲突时很好用。 -
clear:删除全部 stash,难以恢复,慎用。
stash 适合短期切换任务,不是长期备份。长期工作应创建 WIP 分支并提交。
13. 选择性移植:cherry-pick
git cherry-pick <提交>
git cherry-pick A B C
git cherry-pick A..B
git cherry-pick A^..B
git cherry-pick -n <提交>
git cherry-pick -x <提交>
git cherry-pick --continue
git cherry-pick --skip
git cherry-pick --abort
- 将指定提交的变化复制到当前分支,产生新的提交哈希。
-
A..B包含 B、不包含 A;A^..B包含 A 到 B。 -
-n, --no-commit:只应用变化,不立即提交。 -
-x:在提交说明中记录来源提交,适合跨发布分支回补修复。 -
-m <父编号>:挑选合并提交时指定主线。
典型场景:把 main 上的紧急修复同步到维护分支。不要用 cherry-pick 代替正常的长期分支合并,否则容易重复提交。
练习 6:回补线上修复
- 从
v1.0.0建release/1.x。 - 在
main创建一个独立修复提交。 - 在
release/1.x执行git cherry-pick -x <修复提交>。 - 查看两个提交哈希与内容的关系。
14. 查错与定位
14.1 git bisect:二分定位引入缺陷的提交
git bisect start
git bisect bad # 当前版本有问题
git bisect good v1.0.0 # 已知正常版本
# Git 检出中间提交,测试后标记:
git bisect good # 或 git bisect bad
git bisect reset
自动化:
git bisect start HEAD v1.0.0
git bisect run ./test.sh
测试脚本返回 0 表示 good,1–127(125 除外)表示 bad,125 表示无法测试、跳过。二分法定位 1024 个提交最多约需 10 次判断。
14.2 git grep:搜索受 Git 管理的内容
git grep "TODO"
git grep -n "validate" -- "*.js"
git grep -i "error" <提交>
git grep -l "deprecated"
-
-n:显示行号。 -
-i:忽略大小写。 -
-l:只显示文件名。 -
-c:显示匹配次数。 -
-E:扩展正则。
14.3 git shortlog:按作者汇总
git shortlog -sn
git shortlog -sne --all
-
-s:只显示提交数。 -
-n:按数量排序。 -
-e:显示邮箱。
14.4 git describe:根据标签描述版本
git describe --tags
git describe --tags --always --dirty
输出可能为 v1.2.0-5-gabc1234:距标签 5 个提交,当前提交缩写为 abc1234。
15. 子模块、工作树与大文件
15.1 git submodule:仓库中引用另一个仓库
git submodule add <URL> libs/example
git submodule status
git submodule update --init --recursive
git submodule update --remote --merge
git clone --recurse-submodules <URL>
git submodule foreach 'git status --short'
主仓库记录的是子模块的特定提交,而不是其完整文件历史。常见陷阱:克隆后子模块为空、子模块处于分离 HEAD、主仓库忘记提交子模块指针变化。
更新流程:
cd libs/example
git switch main
git pull
cd ../..
git add libs/example
git commit -m "chore: update example submodule"
移除子模块涉及 .gitmodules、暂存区及模块元数据,操作前应先提交或备份并参考当前 Git 手册。
15.2 git worktree:一个仓库同时检出多个分支
git worktree list
git worktree add ../app-hotfix hotfix
git worktree add -b feature/docs ../app-docs main
git worktree remove ../app-hotfix
git worktree prune
适合正在开发功能时并行修复线上问题,避免反复 stash。通常同一分支不能同时被两个工作树检出。
15.3 Git LFS:管理大文件
Git LFS 是独立扩展:
git lfs install
git lfs track "*.psd"
git add .gitattributes
git add design.psd
git commit -m "assets: add design via LFS"
git lfs ls-files
Git 本身不擅长频繁变化的大型二进制文件。启用 LFS 前确认托管平台支持和配额;只写 .gitignore 不能移除已经进入历史的大文件。
15.4 git archive:导出源码快照
git archive --format=zip --output=release.zip v1.0.0
git archive --format=tar.gz --prefix=app-1.0/ -o app.tar.gz v1.0.0
它导出指定提交中的已跟踪文件,不包含 .git 历史。
16. 配置、别名、钩子与忽略规则
16.1 .gitignore
示例:
# 忽略依赖与构建结果
node_modules/
dist/
# 所有日志
*.log
# 但保留示例日志
!example.log
# 仅仓库根目录的环境文件
/.env
# 任意层级缓存目录
**/.cache/
规则:
-
/结尾表示目录。 -
*匹配一段路径内字符,**可跨目录。 -
!取消忽略;若父目录已被完全排除,可能还需先放开父目录。 -
.gitignore只影响未跟踪文件。
文件已经被跟踪时:
git rm --cached .env
git commit -m "chore: stop tracking environment file"
检查忽略来源:
git check-ignore -v path/to/file
个人全局忽略:
git config --global core.excludesFile ~/.config/git/ignore
仅当前仓库、又不想提交的忽略规则可写入 .git/info/exclude。
16.2 .gitattributes
用于指定文本规范、diff 驱动和 LFS 等:
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
改变换行规则后可重新规范化并仔细检查:
git add --renormalize .
git status
git diff --staged
16.3 实用别名
git config --global alias.st "status --short --branch"
git config --global alias.co switch
git config --global alias.br branch
git config --global alias.last "log -1 --stat"
git config --global alias.unstage "restore --staged --"
git config --global alias.lg "log --graph --decorate --oneline --all"
以 ! 开头的别名会交给 shell,能力强但有安全和可移植性风险,不要执行来源不明的别名配置。
16.4 Git Hooks
钩子位于 .git/hooks/,常见:
-
pre-commit:提交前格式化或快速检查。 -
commit-msg:校验提交说明。 -
pre-push:推送前测试。 -
post-merge:合并后执行操作。
钩子默认不随 clone 分发。团队通常借助脚本或工具安装,也可配置:
git config core.hooksPath .githooks
钩子应快速、明确,并与 CI 互补;客户端钩子可被绕过,不能作为唯一安全控制。
16.5 签名
git commit -S -m "feat: signed commit"
git tag -s v1.0.0 -m "Release 1.0.0"
git log --show-signature
git verify-commit <提交>
git verify-tag v1.0.0
签名的密钥类型和托管平台配置因环境而异,可使用 GPG 或受支持的 SSH 签名方案。
17. 团队工作流
17.1 功能分支工作流
main ───────●──────────●─────────
\ /
feature ●──●──●
- 从最新 main 创建短生命周期功能分支。
- 小步提交并推送。
- 定期用 merge 或 rebase 更新。
- 发起评审,通过 CI 后合并。
- 删除已合并分支。
git switch main
git pull --ff-only
git switch -c feature/order-export
# 开发、提交
git push -u origin feature/order-export
17.2 Fork 工作流
git remote -v
git remote add upstream <原项目URL>
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main
git switch -c fix/issue-123
适合开源项目或严格权限隔离团队。
17.3 Trunk-based Development
- 开发者频繁集成到主干。
- 分支保持很短,通常不超过一两天。
- 未完成功能使用 feature flag 隔离。
- 强依赖自动化测试、代码评审和主分支保护。
17.4 Git Flow
包含 main、develop、feature/*、release/*、hotfix/*。适合多个长期维护版本和固定发布窗口;持续交付团队常觉得它过重。
17.5 合并策略比较
| 策略 | 结果 | 优点 | 代价 |
|---|---|---|---|
| Merge commit | 保留分支结构 | 上下文完整,易整体回退 | 历史可能较复杂 |
| Squash merge | 一个综合提交 | 主分支简洁 | 丢失功能分支内部提交关系 |
| Rebase + FF | 线性保留各提交 | 便于逐提交追踪 | 合入前需整理历史和处理冲突 |
团队最重要的不是哪种“绝对最好”,而是统一策略并通过分支保护自动执行。
18. 综合练习案例
案例 A:个人项目完整循环
目标:掌握 init、add、commit、branch、merge、tag。
- 创建
notes-app仓库和 README,完成初始提交。 - 创建
feature/search,新增search.md并分两次提交。 - 回到 main 修改 README。
- 合并功能分支,若没有冲突,刻意修改同一行再重做一次以制造冲突。
- 解决冲突,运行
git log --graph --oneline --all。 - 创建附注标签
v0.1.0。
验收:
git status
git log --graph --decorate --oneline --all
git show v0.1.0
案例 B:把一个混乱提交拆成两个
场景:你同时修改了文档和功能代码,但希望提交历史职责单一。
git add -p
git diff --staged
git commit -m "feat: add search filter"
git add docs/
git commit -m "docs: explain search filter"
若已误提交且尚未推送:
git reset --mixed HEAD~1
git add -p
# 分别提交
验收:两个提交各自可独立理解和回退。
案例 C:团队冲突演练
- 创建裸仓库,再克隆成 alice 和 bob 两份。
- 两人在同一文件同一行写不同内容。
- Alice 先推送。
- Bob 提交后执行
git pull --rebase。 - Bob 解决冲突、
git add、git rebase --continue,再推送。 - 查看远程最终历史。
思考:如果 Bob 使用普通 merge,历史图会有什么不同?
案例 D:线上紧急修复
- 从发布标签
v1.0.0创建hotfix/v1.0.1。 - 修复并测试,提交
fix: ...。 - 合入发布维护分支并打
v1.0.1标签。 - 用 merge 或 cherry-pick 把修复同步回 main。
- 推送分支和标签。
验收:修复同时存在于维护线和未来版本中,提交来源可追踪。
案例 E:用 bisect 定位缺陷
- 创建 10 个顺序提交,其中第 4 个引入错误。
- 写一个能以退出码表示测试成功/失败的小测试脚本。
- 标记当前为 bad,第 1 个提交为 good。
- 手动二分,再用
git bisect run自动二分。 - 执行
git bisect reset回到原分支。
案例 F:恢复误操作
依次练习:
-
restore:丢弃一个工作区修改。 -
restore --staged:取消暂存但保留修改。 -
commit --amend:补进漏掉的文件。 -
revert:撤销一个模拟的已发布提交。 -
reset --hard后用reflog恢复。
要求:每一步执行前后都运行 status、diff 和 log,并用自己的话写出哪个指针或区域发生了变化。
案例 G:从需求到发布的期末项目
- 初始化仓库,配置
.gitignore和.gitattributes。 - 建立
main和三个功能分支,每个分支至少两次原子提交。 - 故意创建并解决一次 merge 冲突。
- 用交互式 rebase 整理一个尚未共享的分支。
- 模拟代码评审后 squash 合并一个分支、普通合并另一个分支。
- 用
cherry-pick -x向维护分支回补修复。 - 创建签名或附注标签并导出源码包。
- 用
reflog完成一次灾难恢复演示。
最终应能解释每个提交为何存在,而不只是让命令“运行成功”。
19. 常见问题与危险命令
19.1 常见报错
fatal: not a git repository
当前目录及父目录没有 .git。先确认路径,或初始化/克隆仓库。
nothing to commit, working tree clean
没有未提交变化;也可能文件被忽略,用 git status --ignored 和 git check-ignore -v 检查。
pathspec ... did not match
路径、分支名或文件名错误;用 git branch --all、git status 和文件列表核对。
non-fast-forward push 被拒绝
远端有本地没有的提交。先 fetch,检查历史,再 merge/rebase;不要立即强推。
refusing to merge unrelated histories
两个仓库没有共同祖先。确认是否选错仓库;确需合并才使用 --allow-unrelated-histories 并仔细解决冲突。
处于 detached HEAD
若要保留提交,立即 git switch -c <新分支>。
19.2 风险等级
| 命令 | 风险 | 安全习惯 |
|---|---|---|
git reset --hard |
丢弃已跟踪的未提交变化 | 先 status/diff,必要时 stash/建分支 |
git clean -fdx |
删除全部未跟踪及忽略文件 | 永远先 git clean -ndx
|
git push --force |
覆盖远程历史 | 用 --force-with-lease,确认目标分支 |
git branch -D |
删除未合并分支引用 | 先 --no-merged,记下提交哈希 |
git stash clear |
删除全部 stash | 先 stash list,逐项 drop |
| 公共分支上 rebase | 改写他人依赖的历史 | 只整理个人未共享提交 |
19.3 提交过敏感信息怎么办
- 立即撤销/轮换密钥、令牌和密码——从 Git 删除不等于秘密未泄露。
- 阻止继续传播并通知负责人。
- 用专用历史重写工具清理所有相关引用。
- 与协作者协调重新克隆或清理旧历史。
- 增加秘密扫描和提交前检查。
历史重写是协作破坏性操作,不能仅靠删除最新文件或普通 git revert 清除旧提交中的秘密。
19.4 操作前的五秒检查
git status --short --branch
git diff
git diff --staged
git log --oneline --decorate -5
git branch -vv
对清理和推送命令优先使用 --dry-run;对历史改写先创建临时保护分支:
git branch backup/before-rewrite
20. 命令速查表与进阶路线
20.1 高频速查
| 目的 | 命令 |
|---|---|
| 初始化 | git init -b main |
| 克隆 | git clone <URL> |
| 查看状态 | git status -sb |
| 暂存全部 | git add -A |
| 按块暂存 | git add -p |
| 提交 | git commit -m "说明" |
| 查看图形历史 | git log --graph --oneline --decorate --all |
| 查看未暂存变化 | git diff |
| 查看已暂存变化 | git diff --staged |
| 创建并切换分支 | git switch -c <分支> |
| 合并 | git merge <分支> |
| 变基 | git rebase <基底> |
| 抓取远程 | git fetch --prune |
| 拉取且只允许快进 | git pull --ff-only |
| 首次推送分支 | git push -u origin <分支> |
| 暂存工作 | git stash push -u -m "说明" |
| 取消暂存 | git restore --staged <文件> |
| 丢弃未暂存修改 | git restore <文件> |
| 安全撤销已共享提交 | git revert <提交> |
| 恢复丢失提交 | git reflog |
| 定位引入缺陷提交 | git bisect |
20.2 命令体系索引
除正文详解的高频命令外,Git 还有以下命令族。学习时按需查手册,不必一次背完:
- 仓库与对象:
init、clone、fsck、gc、count-objects、cat-file、hash-object。 - 快照:
add、commit、rm、mv、restore、reset。 - 分支与整合:
branch、switch、checkout、merge、rebase、cherry-pick、revert。 - 历史查询:
log、show、diff、blame、reflog、shortlog、describe、rev-parse、rev-list。 - 远程:
remote、fetch、pull、push、ls-remote、request-pull。 - 调试:
bisect、grep、range-diff。 - 补丁与邮件:
format-patch、am、apply、send-email。 - 多仓库与多工作区:
submodule、subtree(常以命令扩展提供)、worktree。 - 迁移与集成:
bundle、archive、fast-import、svn(取决于安装)。 - 维护:
maintenance、gc、prune、repack、pack-refs。 - 凭据与签名:
credential、verify-commit、verify-tag。 - 管理:
config、help、version、bugreport、diagnose。
底层 plumbing 命令主要用于脚本、Git 内部机制和高级诊断。日常开发应优先使用 porcelain(面向用户)命令,避免直接修改 .git 内部文件。
20.3 推荐学习顺序
第一阶段:能独立保存版本
掌握 init、status、add、commit、log、diff、.gitignore。
第二阶段:能使用分支开发
掌握 branch、switch、merge、冲突解决、标签。
第三阶段:能安全团队协作
掌握 remote、fetch、pull、push、上游分支和评审工作流。
第四阶段:能纠错和恢复
掌握 restore、revert、三种 reset、reflog、stash。
第五阶段:能整理和诊断历史
掌握交互式 rebase、cherry-pick、bisect、blame、高级 log 范围。
20.4 最终心法
- Git 的核心是提交图、引用和三个本地区域,不是命令口诀。
- 不确定时先看
status、diff、log --graph。 - 共享历史优先用
revert;个人未共享历史才考虑reset/rebase。 -
fetch只下载,适合先观察;pull会继续整合。 - 提交要小、完整、可解释、可测试、可回退。
- 分支要短命,及时集成,减少巨型冲突。
- 强推、硬重置和清理前,先确认对象、范围及恢复方案。
- Git 通常能通过 reflog 救回提交,但未跟踪文件和泄露的秘密是例外。
附录:常用修订表达式
| 表达式 | 含义 |
|---|---|
HEAD |
当前提交 |
HEAD~1 |
当前提交的第一父提交 |
HEAD~3 |
沿第一父链向前三次 |
HEAD^ |
第一个父提交 |
HEAD^2 |
合并提交的第二个父提交 |
main@{yesterday} |
main 昨天在 reflog 中的位置(若可解析) |
tag^{} |
解引用附注标签到其指向对象 |
A..B |
B 可达但 A 不可达的提交 |
A...B |
A 与 B 从共同祖先分开后的两侧提交 |
<提交>:<路径> |
某提交中的文件内容 |
:/<文本> |
提交说明匹配文本的最近提交 |
命令中同时出现修订和路径时,用 -- 消除歧义:
git log main -- src/main.js
git restore -- README.md
建议用一个可随意破坏的练习仓库逐条实验。真正的“精通”不是记住全部参数,而是能在操作前预测工作区、暂存区、分支指针和提交图将如何变化,并在出错后知道如何验证与恢复。