Appearance
附录 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 mainQ2:git reset 和 git revert 有什么区别?
| git reset | git revert | |
|---|---|---|
| 原理 | 移动 HEAD 指针,修改历史 | 创建新提交,撤销旧提交的效果 |
| 是否改变历史 | ✅ 是 | ❌ 否 |
| 适用场景 | 本地未推送的提交 | 已推送的提交 |
| 安全性 | 危险(需谨慎使用 --hard) | 安全 |
bash
# 场景:撤销最近一次提交
# 如果未推送,用 reset
git reset --soft HEAD~1 # 保留修改
# 如果已推送,用 revert
git revert HEAD # 创建新提交撤销Q3:git merge 和 git rebase 有什么区别?
| git merge | git 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.jsQ7:如何修改最后一次提交?
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~1Q9:如何找回被误删的文件?
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 abc1234Q11:如何只暂存部分改动?
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 mainQ14: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 commitQ16:如何将一个提交从一个分支复制到另一个分支?
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 resetQ18:如何查看某行代码是谁写的?
bash
# 查看文件每行的最后修改者
git blame src/user.js
# 查看特定行范围
git blame -L 10,20 src/user.js
# 追踪函数的完整历史
git log -p -L '/function authenticate/','/^}/' src/auth.jsQ19:如何清理本地不需要的内容?
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 -dQ20:如何在不切换分支的情况下临时保存工作?
bash
# 储藏当前修改
git stash
# 可以加描述
git stash push -m "WIP: 用户登录功能,等待 API 就绪"
# 恢复
git stash popQ21:如何减小仓库体积?
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-pathsQ22:如何统计仓库中各人的贡献?
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-leasefatal: not a git repository
bash
# 原因:不在 Git 仓库目录内
# 检查:
git rev-parse --is-inside-work-tree
# 解决:cd 到正确目录,或初始化新仓库
git initCONFLICT (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 --continueYour branch and 'origin/main' have diverged
bash
# 原因:本地和远程同时有新提交
# 解决方案1:rebase(线性历史)
git pull --rebase origin main
# 解决方案2:merge(保留合并历史)
git pull origin main