Skip to content

附录 E:常见问题 FAQ

基础问题

Q1:git pull 和 git fetch 有什么区别?

git fetch 只下载远程数据,不修改本地工作区和当前分支。

git pull = git fetch + git merge(默认)或 git fetch + git rebase(配置 pull.rebase = true 时)。

bash
# 推荐工作流
git fetch origin
git log origin/main  # 查看远程有哪些变更
git merge origin/main  # 确认后再合并

# 或者直接
git pull --rebase origin main

Q2:git reset 和 git revert 有什么区别?

git resetgit revert
原理移动 HEAD 指针,修改历史创建新提交,撤销旧提交的效果
是否改变历史✅ 是❌ 否
适用场景本地未推送的提交已推送的提交
安全性危险(需谨慎使用 --hard)安全
bash
# 场景:撤销最近一次提交
# 如果未推送,用 reset
git reset --soft HEAD~1  # 保留修改

# 如果已推送,用 revert
git revert HEAD  # 创建新提交撤销

Q3:git merge 和 git rebase 有什么区别?

git mergegit rebase
历史保留完整合并历史,有 merge commit线性历史,无 merge commit
适用功能合并到主分支同步主分支变更到功能分支
风险较低改写历史,不要对已推送的提交 rebase

黄金法则:不要对已经推送到公共仓库的提交执行 rebase。

Q4:HEAD、HEAD~1、HEAD^、HEAD^^ 分别是什么意思?

HEAD        当前提交
HEAD~1      上一个提交(父提交)
HEAD~2      上上个提交
HEAD^       上一个提交(与 HEAD~1 相同,对于普通提交)
HEAD^^      上上个提交(与 HEAD~2 相同)

# 区别在合并提交时体现:
HEAD^1      合并提交的第一个父提交(被合并进来的那个分支的提交)
HEAD^2      合并提交的第二个父提交(执行 merge 命令所在的分支的最新提交)

Q5:什么是"游离 HEAD"(Detached HEAD)?

当你 checkout 一个具体的 commit(而非分支名)时,就会进入游离 HEAD 状态:

bash
git checkout abc1234  # 进入游离 HEAD
# 或者
git checkout v1.0.0   # 检出标签也会进入游离 HEAD

# 如果在游离 HEAD 状态下提交了代码,需要创建分支来保存:
git switch -c save-my-work

游离 HEAD 状态下的提交,如果不及时保存到分支,在切换分支后可能会被垃圾回收丢失。


操作问题

Q6:如何撤销"刚刚的 git add"?

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

# 取消暂存指定文件
git restore --staged src/user.js

Q7:如何修改最后一次提交?

bash
# 修改提交信息
git commit --amend

# 添加新文件到最后一次提交(不修改信息)
git add forgotten-file.js
git commit --amend --no-edit

注意:只在未推送时使用,已推送的提交不要 amend。

Q8:如何撤销最近几次提交?

bash
# 撤销最近 1 次提交,保留修改在工作区
git reset --soft HEAD~1

# 撤销最近 3 次提交,保留修改在工作区
git reset --soft HEAD~3

# 撤销并丢弃所有修改(危险!)
git reset --hard HEAD~1

Q9:如何找回被误删的文件?

bash
# 如果文件被 git rm 删除,但未提交
git restore <file>

# 如果已经提交了删除操作
git revert HEAD  # 或者找到文件存在时的提交
git checkout <commit_before_delete> -- <file>

Q10:如何恢复误删的分支?

bash
# 1. 查看 reflog 找到分支最后一次提交的 hash
git reflog

# 2. 创建新分支指向那个提交
git branch recovered-branch abc1234

Q11:如何只暂存部分改动?

bash
# 交互式添加
git add -p

# 会提示每一块(hunk)是否要暂存:
# y - 暂存这一块
# n - 不暂存
# s - 拆分成更小的块
# e - 手动编辑这一块
# q - 退出

Q12:如何将多个提交合并成一个?

bash
# 交互式 rebase
git rebase -i HEAD~4  # 将最近 4 个提交合并

# 在编辑器中,将除第一个外的 pick 改为 squash 或 s
pick abc1234 feat: 添加用户模块
squash def5678 fix: 修复用户查询
squash ghi9012 test: 添加用户测试
squash jkl3456 docs: 更新用户文档

协作问题

Q13:如何同步 Fork 的最新代码?

bash
# 1. 首次:添加上游仓库
git remote add upstream https://github.com/original/repo.git

# 2. 获取上游更新
git fetch upstream

# 3. 合并到本地 main
git switch main
git merge upstream/main

# 4. 推送到自己的 Fork
git push origin main

Q14:PR 提交后如何继续在上面添加修改?

bash
# 在同一功能分支上继续提交
git add <changes>
git commit -m "fix: 根据 review 意见修改"
git push origin feature/my-feature
# PR 会自动更新

Q15:如何解决合并冲突?

bash
# 1. 发生冲突后,查看冲突文件
git status

# 2. 打开冲突文件,找到标记
# <<<<<<< HEAD
# 你的代码
# =======
# 别人的代码
# >>>>>>> feature/other

# 3. 手动编辑,保留需要的代码,删除标记

# 4. 标记为已解决
git add <resolved-file>

# 5. 继续合并
git merge --continue
# 或者
git commit

Q16:如何将一个提交从一个分支复制到另一个分支?

bash
# cherry-pick 单个提交
git switch target-branch
git cherry-pick abc1234

# cherry-pick 多个提交
git cherry-pick abc1234 def5678

# cherry-pick 一个范围(不含 A,含 B)
git cherry-pick A..B

进阶问题

Q17:如何找出哪个提交引入了 Bug?

使用 git bisect 二分查找:

bash
git bisect start
git bisect bad           # 当前版本有 bug
git bisect good v1.0.0   # 这个版本没有 bug

# Git 会自动检出中间版本,测试后标记:
git bisect good  # 或
git bisect bad

# 找到问题提交后
git bisect reset

Q18:如何查看某行代码是谁写的?

bash
# 查看文件每行的最后修改者
git blame src/user.js

# 查看特定行范围
git blame -L 10,20 src/user.js

# 追踪函数的完整历史
git log -p -L '/function authenticate/','/^}/' src/auth.js

Q19:如何清理本地不需要的内容?

bash
# 查看未跟踪文件(预览)
git clean -n

# 删除未跟踪文件
git clean -f

# 删除未跟踪文件和目录
git clean -fd

# 删除包括 .gitignore 忽略的文件
git clean -fdx

# 清理已删除的远程分支的本地追踪
git fetch --prune

# 删除本地所有已合并到 main 的分支
git branch --merged main | grep -v '^\*\|main' | xargs -n 1 git branch -d

Q20:如何在不切换分支的情况下临时保存工作?

bash
# 储藏当前修改
git stash

# 可以加描述
git stash push -m "WIP: 用户登录功能,等待 API 就绪"

# 恢复
git stash pop

Q21:如何减小仓库体积?

bash
# 1. 垃圾回收
git gc --aggressive

# 2. 清理悬空对象
git prune

# 3. 查看大文件(历史中)
git rev-list --objects --all | sort -k 2 | cut -f 2 -d\  | xargs -I{} git cat-file --batch-check | sort -k 3 -n | tail -20

# 4. 使用 git-filter-repo 删除历史中的大文件
pip install git-filter-repo
git filter-repo --path-glob '*.zip' --invert-paths

Q22:如何统计仓库中各人的贡献?

bash
# 按提交数统计
git shortlog -sn

# 按行数统计(需要 git log 和其他工具配合)
git log --author="Name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }'

# 仓库摘要(需要 git-extras)
git summary

错误信息处理

error: failed to push some refs

bash
# 原因:远程有你本地没有的提交
# 解决:先拉取再推送
git pull --rebase
git push

# 或者(谨慎)强制推送,可能覆盖他人工作
git push --force-with-lease

fatal: not a git repository

bash
# 原因:不在 Git 仓库目录内
# 检查:
git rev-parse --is-inside-work-tree

# 解决:cd 到正确目录,或初始化新仓库
git init

CONFLICT (content): Merge conflict in file

bash
# 查看所有冲突文件
git diff --name-only --diff-filter=U

# 使用 mergetool 图形化解决
git mergetool

# 解决后
git add <files>
git commit  # 或 git merge --continue / git rebase --continue

Your branch and 'origin/main' have diverged

bash
# 原因:本地和远程同时有新提交
# 解决方案1:rebase(线性历史)
git pull --rebase origin main

# 解决方案2:merge(保留合并历史)
git pull origin main