Git 零基础完全掌握指南
从"完全不会"到"独立解决问题",一份覆盖命令、实战、排错、面试的 Git 自学文档。
目录
- 第一部分:入门概念
- 第二部分:本地基本功
- 第三部分:分支与合并
- [第四部分:远程仓库与 GitHub](#第四部分:远程仓库与 GitHub)
- 第五部分:历史与调试
- 第六部分:常见问题排查
- 第七部分:面试高频问题
- 第八部分:练习任务
- 第九部分:速查表
- 附录
第一部分:入门概念
1.1 什么是版本控制
想象你在写一篇论文:
论文_v1.docx
论文_v2_修改引言.docx
论文_v3_导师批注.docx
论文_v3_最终版.docx
论文_v3_最终版_真的最终.docx
这就是最原始的"版本控制"------用文件名记录不同版本。问题是:文件多了会乱、不知道每个版本改了什么、回不去某个历史状态。
版本控制系统(VCS) 就是来自动做这件事的工具。它记录文件的每一次变化,让你可以:
- 回到任意历史版本
- 查看谁在什么时候改了什么
- 多人同时协作编辑而不互相覆盖
1.2 Git 是什么,为什么用它
Git 是一个分布式版本控制系统,由 Linus Torvalds(Linux 之父)于 2005 年创建。
分布式 vs 集中式:
集中式(SVN):
所有人的代码 ↔ 中央服务器
服务器挂了 → 所有人都无法工作
分布式(Git):
每个人本地都有完整的仓库和历史
服务器挂了 → 每个人本地还能工作,
恢复后用本地仓库推上去即可
为什么 Git 是行业标准?
- 快:绝大部分操作在本地完成,不需要网络
- 安全:SHA-1 哈希确保数据完整性,改一个字节都能发现
- 灵活:分支创建几乎零成本
- 生态:GitHub、GitLab、Gitee 等平台构建了完整的协作生态
1.3 Git 的三种状态和三棵树
这是 Git 最重要的心智模型,必须理解。
文件在 Git 中有三种状态:
工作区 暂存区 仓库
(Working (Staging (Repository)
Directory) Area) .git/
[ 写代码的地方 ] --add--> [ 准备提交的地方 ] --commit--> [ 历史记录 ]
git add git commit
<--------- git checkout / git restore ----------------
- 工作区(Working Directory):你在编辑器中看到的文件,正在修改中的状态
- 暂存区(Staging Area / Index):一个中间区域,你决定哪些修改要放入下一次提交
- 仓库(Repository / .git 目录):Git 存储所有历史版本的地方
为什么要有暂存区? 它让你能精细控制每次提交包含哪些修改。比如你改了 5 个文件,但只想把其中 2 个作为一次提交,另外 3 个作为另一次提交------暂存区让你能做到这一点。
一句话总结 :工作区写代码 →
git add放入暂存区 →git commit存入仓库。
1.4 安装 Git
Windows:
- 去 git-scm.com 下载安装包
- 一路默认安装(推荐勾选 "Git Bash Here",它会给你一个 Linux 风格的终端)
- 验证:打开 Git Bash,输入
git --version
Mac:
bash
# 方式一:Xcode Command Line Tools(推荐)
xcode-select --install
# 方式二:Homebrew
brew install git
Linux(Ubuntu/Debian):
bash
sudo apt update
sudo apt install git
验证安装:
bash
git --version
# 输出类似:git version 2.45.0
1.5 首次配置
安装完后必须配置用户名和邮箱------每次提交都会附上这些信息:
bash
git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"
--global表示对该计算机上所有仓库生效。对单个仓库设置可去掉--global。
推荐额外配置:
bash
# 设置默认分支名为 main(GitHub 的标准)
git config --global init.defaultBranch main
# Windows 用户必须设置:防止换行符问题
git config --global core.autocrlf true
# 显示中文文件名(避免乱码)
git config --global core.quotepath false
# 设置默认编辑器(提交信息时用)
git config --global core.editor "code --wait" # VS Code
# 常用别名(省时间)
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.lg "log --oneline --graph --all"
查看所有配置:
bash
git config --list
1.6 获取帮助
学会自己查文档,就不需要死记硬背:
bash
# 查看完整文档(按 q 退出)
git help <命令>
# 例如:git help commit
# 查看简略帮助
git <命令> -h
# 例如:git commit -h
# 查看具体选项
git <命令> --help
第二部分:本地基本功
2.1 git init --- 初始化仓库
把一个普通文件夹变成 Git 仓库:
bash
mkdir my-project
cd my-project
git init
# 输出:Initialized empty Git repository in .../my-project/.git/
执行后会创建一个隐藏文件夹 .git,Git 的所有数据都存在里面。
如果你不小心 git init 在了错误的位置 ,直接删除 .git 文件夹即可恢复成普通文件夹:
bash
rm -rf .git # Linux/Mac/Git Bash
2.2 git clone --- 克隆仓库
把远程仓库完整下载到本地:
bash
# HTTPS 方式
git clone https://github.com/用户名/仓库名.git
# SSH 方式(需先配置 SSH Key)
git clone git@github.com:用户名/仓库名.git
# 克隆到指定文件夹
git clone https://github.com/用户名/仓库名.git my-folder
clone 做了三件事:
- 下载所有文件
- 下载完整的提交历史
- 自动创建名为
origin的远程连接
2.3 git status --- 查看状态
最重要的命令,没有之一。 任何时候不知道该做什么了,先敲 git status:
bash
git status
它会告诉你:
- 你在哪个分支
- 有哪些文件被修改了但没暂存(红色)
- 有哪些文件在暂存区等待提交(绿色)
- 有哪些文件没被跟踪(Untracked files)
简洁模式:
bash
git status -s
# M file.txt -- 已修改未暂存
# M file2.txt -- 已暂存(第一列有 M)
# ?? newfile.txt -- 未跟踪
2.4 git add --- 添加到暂存区
bash
# 添加单个文件
git add 文件名
# 添加多个文件
git add file1.txt file2.txt
# 添加当前目录下所有修改
git add .
# 添加所有修改(包括上层目录)
git add -A
# 交互式添加(逐个确认每个修改是否暂存)
git add -p
2.5 git commit --- 提交
把暂存区的内容保存到仓库,形成一个版本快照:
bash
# 最基本用法
git commit -m "描述你做了什么"
# 跳过 git add,直接提交所有已跟踪文件的修改
git commit -a -m "描述"
# 修改上一次提交(追加修改或改提交信息)
git commit --amend -m "新的提交信息"
git commit --amend --no-edit # 追加修改但不改信息
提交信息怎么写? 遵循"约定式提交"(Conventional Commits)规范:
<类型>: <简短描述>
类型包括:
- feat: 新功能
- fix: 修复 bug
- docs: 文档修改
- refactor: 重构(不改变功能)
- style: 格式修改(不影响代码运行)
- test: 添加或修改测试
- chore: 构建或辅助工具的变动
好例子:
feat: 添加用户登录功能
fix: 修复订单金额计算错误
docs: 更新安装说明
git commit --amend只适合修改还没有推送到远程 的提交。如果已经 push 了,不要用--amend,否则需要--force push(危险操作)。
2.6 git log --- 查看历史
bash
# 基础日志
git log
# 一行一条(最常用)
git log --oneline
# 图形化显示分支历史
git log --oneline --graph --all
# 查看最近 5 条
git log -5
# 查看某个文件的修改历史
git log -- 文件名
# 按关键词搜索提交信息
git log --grep="关键词"
# 查看某个作者的所有提交
git log --author="名字"
# 自定义格式
git log --pretty=format:"%h - %an, %ar : %s"
# %h = 简短哈希, %an = 作者, %ar = 相对时间, %s = 提交信息
2.7 git diff --- 查看差异
比"改了哪里"更重要的是理解不同的比较范围:
| 命令 | 比较范围 |
|---|---|
git diff |
工作区 vs 暂存区(还没 add 的内容) |
git diff --staged |
暂存区 vs 最新提交(add 了但没 commit) |
git diff HEAD |
工作区 vs 最新提交(所有未提交的修改) |
git diff 提交A 提交B |
两次提交之间的差异 |
git diff 分支A..分支B |
两个分支之间的差异 |
2.8 git reset --- 回退
三个模式的区别:
--soft: 只移动 HEAD → 修改回到暂存区(绿色)
--mixed: 移动 HEAD + 重置暂存区 → 修改回到工作区(红色)【默认】
--hard: 移动 HEAD + 重置暂存区 + 重置工作区 → 修改全部丢弃 ⚠️
bash
# 撤销最近一次提交,修改回到暂存区
git reset --soft HEAD~1
# 撤销最近一次提交 + 暂存,修改回到工作区
git reset HEAD~1
# 等价于
git reset --mixed HEAD~1
# 撤销最近一次提交,修改全部丢弃(危险!)
git reset --hard HEAD~1
# 回到某个指定提交
git reset --hard abc1234
# 从暂存区移除某个文件(不影响工作区)
git reset HEAD 文件名
2.9 git revert --- 安全撤销
和 reset 不同,revert 会创建一个新的提交来撤销之前的修改,历史不会被改写:
bash
git revert abc1234
# 如果已经 push 到远程,必须用 revert 而不是 reset
什么时候用 reset vs revert?
| 场景 | 用什么 |
|---|---|
| 撤销还没 push 的提交 | git reset |
| 撤销已经 push 的提交 | git revert |
| 想把几个提交合并成一个 | git reset --soft |
| 丢弃所有本地修改 | git reset --hard |
2.10 git rm / git mv --- 删除与移动
bash
# 删除文件(同时从工作区和暂存区删除)
git rm 文件名
# 只从暂存区删除,保留工作区文件(停止跟踪)
git rm --cached 文件名
# 移动/重命名文件
git mv 旧文件名 新文件名
# 等价于 mv 旧文件名 新文件名 && git add 旧文件名 新文件名
2.11 .gitignore --- 忽略文件
有些文件不想让 Git 跟踪(编译产物、依赖包、密钥、系统文件等),创建一个 .gitignore 文件:
gitignore
# 注释用 # 开头
# 忽略特定文件
secret.key
.env
# 忽略整个文件夹
node_modules/
dist/
build/
# 使用通配符
*.log
*.tmp
# 否定模式(不忽略)
!important.log
# 忽略某文件夹下所有 .txt
docs/**/*.txt
常用 .gitignore 模板见附录。
技巧 :在 GitHub 创建新仓库时可以直接选择
.gitignore模板。
第三部分:分支与合并
3.1 分支的本质
分支就是一个可移动的指针,指向某个提交。
main
↓
A -- B -- C
↑
feature/login
每个分支指向一个提交
当你在某个分支上做新提交时,指针自动向前移动
分支如此轻量,正是 Git 比其他版本控制系统强大得多的原因------创建、切换、合并分支几乎瞬间完成,鼓励你频繁使用分支。
3.2 git branch --- 分支管理
bash
# 列出本地所有分支
git branch
# 列出所有分支(包括远程)
git branch -a
# 创建新分支
git branch 分支名
# 创建并切换到新分支
git switch -c 分支名
# 或旧写法:git checkout -b 分支名
# 删除分支(已合并的)
git branch -d 分支名
# 强制删除分支(即使未合并)
git branch -D 分支名
# 重命名分支
git branch -m 旧名称 新名称
3.3 切换分支:git switch vs git checkout
Git 2.23+ 引入了更清晰的 git switch 和 git restore,将 checkout 的两种职责分开:
| 操作 | 新命令 | 旧命令 |
|---|---|---|
| 切换分支 | git switch 分支名 |
git checkout 分支名 |
| 创建并切换 | git switch -c 新分支 |
git checkout -b 新分支 |
| 恢复文件 | git restore 文件名 |
git checkout -- 文件名 |
| 恢复暂存区 | git restore --staged 文件名 |
git reset HEAD 文件名 |
建议 :学习新命令。
checkout一个词干了太多事,新命令语义更清晰。
3.4 git merge --- 合并分支
把一个分支的修改合并到当前分支:
bash
# 1. 先切换到目标分支
git switch main
# 2. 合并 feature 分支
git merge feature/login
两种合并方式:
Fast-forward(快进合并):当目标分支没有新提交时,直接把指针前移。历史是一条直线。
合并前:
main → A -- B
↑ feature
合并后:
main/feature → A -- B
Three-way merge(三方合并):当两个分支都有新提交时,创建一个新的"合并提交"。
合并前:
main → A -- B -- C
\
D -- E ← feature
合并后:
main → A -- B -- C ----- M
\ /
D ----- E ---
↑ feature
禁用 fast-forward(保留分支痕迹):
bash
git merge --no-ff feature/login
3.5 合并冲突
当两个分支修改了同一个文件的同一行,Git 不知道以哪个为准,就产生冲突。
冲突标记:
<<<<<<< HEAD
当前分支(main)的内容
=======
被合并分支(feature)的内容
>>>>>>> feature/login
解决步骤:
- 打开冲突文件,找到
<<<<<<<和>>>>>>>标记 - 决定保留哪个版本(或合并两者),删除标记
git add 文件名(标记冲突已解决)git commit(完成合并)
如果不想解决,放弃合并:
bash
git merge --abort
使用可视化工具:
bash
git mergetool # 会打开配置的合并工具
3.6 git rebase --- 变基
rebase 将当前分支的提交"移植"到另一个分支的顶端,使历史变成一条直线:
bash
# 在 feature 分支上
git rebase main
rebase 前:
main → A -- B -- C
\
D -- E ← feature
rebase 后:
main → A -- B -- C
\
D' -- E' ← feature
(D' 和 E' 是新生成的提交,内容相同但哈希不同)
merge vs rebase:
| merge | rebase | |
|---|---|---|
| 历史记录 | 保留真实的分支历史 | 变成一条直线 |
| 冲突解决 | 一次解决 | 可能要对每个提交各解决一次 |
| 什么时候用 | 公共分支、多人协作 | 个人分支整理 |
| 安全性 | 安全 | 改写历史,有风险 |
黄金法则:不要对已经 push 到公共仓库的提交做 rebase!
3.7 git cherry-pick --- 摘樱桃
把某个提交"复制"到当前分支:
bash
git cherry-pick 提交哈希
典型场景:在错误分支上做了提交,想把其中一个提交搬到正确分支上。
3.8 git stash --- 暂存工作现场
工作中突然要切换分支,但当前修改还没到可以提交的程度:
bash
# 暂存当前修改
git stash
# 或带描述信息
git stash push -m "正在开发登录功能"
# 查看暂存列表
git stash list
# 恢复最近一次暂存(不删除 stash)
git stash apply
# 恢复最近一次暂存(删除 stash)
git stash pop
# 恢复指定的 stash
git stash apply stash@{2}
# 删除指定的 stash
git stash drop stash@{1}
# 清空所有 stash
git stash clear
3.9 git tag --- 标签
给特定提交打上版本号标记:
bash
# 轻量标签(只是一个指针)
git tag v1.0.0
# 附注标签(包含作者、日期、说明,推荐)
git tag -a v1.0.0 -m "第一个正式版本"
# 查看所有标签
git tag
# 查看标签详情
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 # 同时删除远程标签
第四部分:远程仓库与 GitHub
4.1 GitHub 是什么
GitHub 是全球最大的代码托管平台(2018 年被微软收购),基于 Git 构建。截至 2024 年拥有超过 1 亿开发者。
GitHub ≠ Git:
- Git 是版本控制工具(运行在你电脑上)
- GitHub 是代码托管网站(运行在云端)
- 类似的平台:GitLab、Gitee(码云)、Bitbucket
学好 Git 才能用好 GitHub,两者是互补关系。
4.2 SSH Key 配置
每次 push/pull 都要输密码很烦,配置 SSH Key 实现免密:
bash
# 1. 生成 SSH Key(一路回车)
ssh-keygen -t ed25519 -C "你的邮箱@example.com"
# 2. 复制公钥
cat ~/.ssh/id_ed25519.pub
# 3. 打开 GitHub → Settings → SSH and GPG keys → New SSH key
# 粘贴公钥,Title 随便填
# 4. 测试连接
ssh -T git@github.com
# 成功输出:Hi 用户名! You've successfully authenticated...
4.3 git remote --- 管理远程仓库
bash
# 查看远程仓库
git remote -v
# 添加远程仓库
git remote add origin git@github.com:用户名/仓库名.git
# 修改远程地址
git remote set-url origin git@github.com:用户名/新仓库名.git
# 删除远程连接
git remote remove origin
# 查看远程仓库详情
git remote show origin
origin是约定俗成的默认远程名称,你可以叫任何名字,但大家都用origin。
4.4 git push --- 推送到远程
bash
# 推送当前分支到远程
git push origin 分支名
# 首次推送并设置上游(之后只用 git push)
git push -u origin main
# 推送所有分支
git push --all
# ⚠️ 强制推送(危险!会覆盖远程历史)
git push --force
# 安全版强制推送(只在没有新提交时生效)
git push --force-with-lease
4.5 git fetch vs git pull
bash
# fetch: 只下载远程更新,不合并
git fetch origin
# pull = fetch + merge(两步合为一步)
git pull origin main
# pull 用 rebase 而不是 merge(保持历史整洁)
git pull --rebase origin main
建议 :初次学习使用
pull,团队协作时养成先fetch再决定要不要merge的习惯,更安全。
4.6 Pull Request(PR)工作流
PR 不是 Git 的命令,而是 GitHub 的功能------请求别人拉取你的代码:
你(贡献者):
1. Fork 原仓库(在 GitHub 网页上点一下)
2. git clone 你 fork 的仓库
3. git switch -c feature/my-change
4. 写代码 → git add → git commit
5. git push origin feature/my-change
6. 在 GitHub 上点击 "New Pull Request"
仓库维护者:
7. Code Review(代码审查)
8. 要求修改 or 批准
9. 点击 "Merge Pull Request"
4.7 GitHub 核心功能
Issues(问题追踪):
- 报告 bug、提出功能需求、讨论技术方案
- 可以分配给成员、打标签、关联 PR
- 在 PR 描述中写
Closes #123,合并后自动关闭对应 Issue
Pull Requests(代码审查):
- PR 页面可以逐行评论
- 使用 "Suggestions" 直接提议代码修改
- 设置为 Draft PR 表示"还在开发中,不要合并"
Actions(CI/CD):
- 自动化工作流:代码推送 → 自动测试 → 自动部署
- 在
.github/workflows/目录下定义 YAML 文件 - 常见用途:运行测试、代码检查、自动发布
Projects(看板管理):
- 类似 Trello,拖拽卡片管理任务
- 可关联 Issues 和 PR
Discussions(社区讨论):
- 不适合发 Issue 的开放性讨论放这里
- 比如:"这个设计用方案 A 还是方案 B?"
Gist(代码片段):
- 快速分享一段代码,不需要创建仓库
- 可以设为公开或私密
4.8 GitHub 个人主页美化
创建一个和你用户名同名的仓库,里面的 README.md 会自动展示在你的个人主页:
- 新建仓库,名称 = 你的 GitHub 用户名
- 勾选 "Add a README file"
- 编辑
README.md,加入你的自我介绍
推荐内容:
- 技术栈、正在学习的内容
- 常用工具
- GitHub 统计卡片(使用 github-readme-stats)
4.9 开源协作完整工作流
1. 找到项目 → 阅读 README.md 和 CONTRIBUTING.md
2. Fork 仓库(在 GitHub 网页上点,变成你自己的副本)
3. Clone 到本地:git clone git@github.com:你的用户名/项目名.git
4. 添加上游:git remote add upstream https://github.com/原作者/项目名.git
5. 创建分支:git switch -c fix/bug-description
6. 写代码 + 提交
7. 同步上游:git fetch upstream && git rebase upstream/main
8. Push:git push origin fix/bug-description
9. 在 GitHub 上创建 PR(从你的 fork 到原仓库)
10. 等待 Code Review,根据反馈修改
11. PR 被合并后,同步你的 fork 和本地仓库
第五部分:历史与调试
5.1 git blame --- 追踪代码来源
查看文件中每一行是谁、什么时候、在哪次提交中写的:
bash
git blame 文件名
# 查看指定行范围
git blame -L 10,50 文件名
# 忽略空白修改(真正找到改了逻辑的人)
git blame -w 文件名
# 查看文件在某个历史版本的内容
git blame 提交哈希 -- 文件名
blame不是用来甩锅的,而是用来理解代码背景------找到作者后可以直接问"为什么这么写"。
5.2 git bisect --- 二分法定位 bug
当你知道某个版本是好的,某个版本有 bug,但不知道是哪个提交引入的:
bash
# 1. 启动二分查找
git bisect start
# 2. 标记坏版本(当前有 bug)
git bisect bad
# 3. 标记好版本
git bisect good abc1234
# 4. Git 自动切到中间某个版本
# 测试 → git bisect good(如果没 bug)
# git bisect bad(如果有 bug)
# 重复直到找到第一个出问题的提交
# 5. 结束
git bisect reset
5.3 git reflog --- 后悔药
reflog 记录了你所有操作的日志 ,即使 git reset --hard 丢掉了提交,也能通过 reflog 找回:
bash
git reflog
# 输出示例:
# abc1234 HEAD@{0}: commit: 添加登录功能
# def5678 HEAD@{1}: reset: moving to HEAD~3
# ghi9012 HEAD@{2}: commit: 临时保存(已被 reset 丢掉)
# 找回丢失的提交
git checkout -b recovered-branch ghi9012
reflog 是本地记录,push/pull 操作不会同步 reflog。有效期为 90 天。
5.4 git show --- 查看提交详情
bash
# 查看最近一次提交的详细内容
git show
# 查看指定提交
git show abc1234
# 只看指定文件在该提交中的变化
git show abc1234 -- 文件名
# 只看提交的统计信息
git show --stat abc1234
5.5 git grep --- 在仓库中搜索
比系统 grep 快,且只在 Git 跟踪的文件中搜索:
bash
# 搜索关键词
git grep "function name"
# 显示行号
git grep -n "TODO"
# 显示匹配数量
git grep -c "import"
# 在某个历史版本中搜索
git grep "old function" abc1234
第六部分:常见问题排查
以下每个问题都按 现象 → 原因 → 解决方案 的结构组织。
遇到问题时,第一步永远是
git status。
6.1 提交到了错误的分支
现象 :在 main 分支上写完了代码并提交了,但应该在 feature 分支上。
解决方案:
bash
# 1. 记下当前提交的哈希(假设是 abc1234)
git log --oneline -1
# 2. 创建正确的分支指向这个提交
git switch -c feature/正确分支名
# 3. 回到 main,撤销那个提交
git switch main
git reset --hard HEAD~1
# 或者反过来,用 cherry-pick:
# 在正确的分支上 git cherry-pick abc1234
# 然后在错误的分支上 git reset --hard HEAD~1
6.2 commit message 写错了
现象:刚提交完发现信息写错了。
解决方案(还没 push):
bash
git commit --amend -m "正确的提交信息"
如果已经 push 了 :不要用 --amend,除非只有你一个人在这个分支上工作。安全做法是用 git revert 再重新提交,或者接受这个错误。
6.3 误删了文件
现象:不小心删了文件或者改了文件想恢复。
解决方案:
bash
# 恢复工作区的文件到最近一次提交的状态
git restore 文件名
# 旧命令写法
git checkout -- 文件名
# 恢复指定版本的文件
git restore --source=abc1234 文件名
6.4 git push 被拒绝
现象:
! [rejected] main -> main (fetch first)
error: failed to push some refs
原因:远程有别人推送的更新,你的本地落后了。
解决方案:
bash
# 1. 先拉取远程更新
git pull --rebase origin main
# 2. 如果有冲突,解决后继续
git add .
git rebase --continue
# 3. 再推送
git push origin main
6.5 detached HEAD 状态
现象:
You are in 'detached HEAD' state.
原因:你 checkout 到了一个具体的提交而不是分支。HEAD 指向了一个提交哈希而非分支名。
解决方案:
bash
# 如果要做修改,创建新分支
git switch -c 新分支名
# 如果只是看看,看完切回去
git switch main
6.6 合并冲突反复发生
现象:每次 merge 或 rebase 都产生同样的冲突。
解决方案 :启用 git rerere(reuse recorded resolution):
bash
git config --global rerere.enabled true
开启后,Git 会记住你解决过的冲突。下次同样的冲突出现时会自动按之前的方式解决,只留下已暂存的文件等你 review。
6.7 不小心 git add 了不该加的文件
现象:把大文件、密钥或临时文件加入了暂存区。
解决方案:
bash
# 从暂存区移除(保留工作区文件)
git restore --staged 文件名
# 或旧写法
git reset HEAD 文件名
# 如果已经 commit 了,修改 .gitignore 然后
git rm --cached 文件名
git commit -m "chore: 移除不该跟踪的文件"
6.8 大文件误提交
现象:提交了一个几百 MB 的文件,仓库变得巨大。
解决方案:
bash
# 如果还没 push
git reset --soft HEAD~1
echo "大文件名" >> .gitignore
git add .gitignore
git commit -m "chore: 添加 .gitignore"
# 如果已经 push,需要用工具
# 推荐使用 git-filter-repo(比 filter-branch 更快)
# pip install git-filter-repo
git filter-repo --path 大文件名 --invert-paths
git push origin --force
6.9 Windows 换行符问题
现象 :git diff 显示整个文件都改了,但实际只改了几行。
原因 :Windows 用 CRLF(\r\n),Linux/Mac 用 LF(\n)。
解决方案:
bash
# Windows 用户
git config --global core.autocrlf true
# 提交时 CRLF → LF,检出时 LF → CRLF
# Mac/Linux 用户
git config --global core.autocrlf input
# 提交时 CRLF → LF,检出时不变
如果仓库中已经有换行符问题:
bash
# 创建 .gitattributes 文件
echo "* text=auto" > .gitattributes
git add .gitattributes
git commit -m "chore: 统一换行符"
6.10 中文乱码
现象 :git status 中中文文件名显示为 \344\270\255\346\226\207。
解决方案:
bash
git config --global core.quotepath false
第七部分:面试高频问题
7.1 git merge 和 git rebase 的区别?什么时候用哪个?
- merge:创建一个合并提交,保留完整的分支历史。适合公共分支、多人协作。
- rebase:将当前分支的提交"重放"到目标分支顶端,历史变成一条直线。适合个人分支整理。
- 选择 :本地未 push 的提交用 rebase 整理干净,公共分支用 merge 保留历史。绝不对已 push 的提交做 rebase。
7.2 git reset 和 git revert 的区别?
- reset:移动 HEAD 指针,可以改写历史。适合撤销还没 push 的提交。
- revert:创建一个新的提交来撤销之前的修改,不改写历史。适合已 push 的提交。
- 关键区别:revert 是安全的(不破坏历史),reset 可以彻底删除提交。
7.3 git fetch 和 git pull 的区别?
- fetch:只下载远程更新到本地,不修改工作区。你可以先查看更新内容再做决定。
- pull = fetch + merge(或 fetch + rebase),直接把远程更新合并到本地。
- 建议:日常用 pull,但在重要操作前先 fetch 查看变化。
7.4 fast-forward merge 是什么?
当被合并的分支直接位于目标分支的历史路径上时,Git 只需把目标分支的指针前移,不需要创建新的合并提交。就像顺着一条直线往前走了一步,没有分叉。
7.5 如何修改最后一次提交?
bash
git commit --amend -m "新信息" # 改信息
git commit --amend --no-edit # 追加修改不改信息
注意:只适用于还没 push 的提交。
7.6 .git 目录里有什么?
.git/
├── HEAD # 指向当前分支
├── config # 仓库配置
├── objects/ # 所有数据(blob、tree、commit)
├── refs/ # 分支和标签的指针
│ ├── heads/ # 本地分支
│ └── tags/ # 标签
├── index # 暂存区
├── hooks/ # 钩子脚本
└── logs/ # reflog 日志
7.7 Git 如何存储文件?
Git 是内容寻址文件系统 。每个对象(文件内容 blob、目录结构 tree、提交信息 commit)都以 SHA-1 哈希作为文件名存储在 .git/objects/ 中。
commit: 包含 tree 的哈希、父提交、作者、提交信息
↓
tree: 包含文件名和对应 blob 的哈希
↓
blob: 文件的完整内容(压缩后)
同一个文件内容只存一次,不管出现多少次。Git 存储的是快照而非差异。
7.8 detached HEAD 是什么?如何解决?
HEAD 直接指向了一个具体的提交哈希而不是分支名。这通常发生在 git checkout 提交哈希 后。
在此状态下的提交不会被任何分支引用,切换分支后可能丢失。解决:git switch -c 新分支名。
7.9 什么是 Git Flow / GitHub Flow?
- Git Flow:重型分支模型。分支类型有 main、develop、feature、release、hotfix。适合有固定发布周期的项目。
- GitHub Flow:轻量模型。只有一个 main 分支 + 短期 feature 分支。通过 PR 合并,合并后立即部署。适合持续部署的项目。
- 大多数现代团队使用简化版的 GitHub Flow 或 Trunk-based Development。
7.10 如何合并多个 commit?
使用交互式 rebase 的 squash:
bash
git rebase -i HEAD~3 # 合并最近 3 个提交
# 编辑器中将后两个的 pick 改为 squash(或 s)
# pick abc1234 第一个提交
# squash def5678 第二个提交 → 改为 s
# squash ghi9012 第三个提交 → 改为 s
# 保存后编辑合并后的提交信息
7.11 git stash 的作用和常用命令?
临时保存工作区修改,让工作区变干净,以便切换分支或做其他操作。
bash
git stash # 暂存
git stash pop # 恢复并删除
git stash list # 查看列表
git stash drop stash@{1} # 删除指定
7.12 冲突如何产生?如何解决?
两个分支修改了同一文件的同一区域时,Git 无法自动合并。
解决:手动编辑冲突文件,删除 <<<<<<< / ======= / >>>>>>> 标记,选择保留的内容,然后 git add + git commit。
7.13 HEAD、ORIG_HEAD、FETCH_HEAD 分别是什么?
- HEAD:指向当前所在分支(或提交)的指针。
- ORIG_HEAD:记录危险操作(merge、reset、rebase)前的 HEAD 位置,用于撤销。
- FETCH_HEAD :记录最近一次
git fetch获取到的远程分支的最新提交。
7.14 "Git 是内容寻址文件系统"是什么意思?
Git 的核心是一个键值对数据库。每个对象(文件、目录、提交)通过内容的 SHA-1 哈希来寻址。相同内容一定产生相同哈希,所以 Git 天然支持去重和完整性校验。
7.15 git reflog 的作用?
记录本地仓库中 HEAD 和分支引用的所有变更历史。即使通过 git reset --hard 丢弃了提交,也能通过 reflog 找回。这是 Git 的最后一道防线------90 天内理论上不会丢失任何提交。
第八部分:练习任务
建议在本地创建一个测试仓库
~/git-practice来练习,放心大胆操作------错了就删掉重建。
练习 1:第一次提交
目标 :掌握 init → add → commit → log 流程。
bash
mkdir ~/git-practice && cd ~/git-practice
git init
echo "# 我的 Git 练习" > README.md
git status # 看到 untracked file
git add README.md
git status # 看到 staged file
git commit -m "feat: 初始化项目"
git log --oneline # 看到你的提交
✅ 检查点:git log 能看到一次提交。
练习 2:分支操作(模拟 feature 开发)
目标:掌握分支的创建、切换、合并。
bash
# 创建并切换到 feature 分支
git switch -c feature/hello
# 写点代码
echo "print('Hello, Git!')" > hello.py
git add hello.py && git commit -m "feat: 添加 hello 脚本"
# 切回 main,合并
git switch main
git merge feature/hello
# 删除 feature 分支
git branch -d feature/hello
# 查看历史(应该是一条直线)
git log --oneline --graph --all
✅ 检查点:合并后 git log --oneline 能看到你的两个提交。
练习 3:解决冲突
目标:故意制造冲突并手动解决。
bash
# 创建两个分支,各自修改 README.md 的第一行
git switch -c branch-a
echo "这是 A 分支的标题" > README.md
git commit -am "feat: A 的修改"
git switch main
git switch -c branch-b
echo "这是 B 分支的标题" > README.md
git commit -am "feat: B 的修改"
# 尝试合并 → 必然冲突
git switch main
git merge branch-a # 这次成功
git merge branch-b # 冲突!
# 打开 README.md,手动编辑解决冲突
# 删除冲突标记,保存最终版本
git add README.md
git commit -m "merge: 合并 branch-b,解决冲突"
✅ 检查点:成功解决冲突并完成合并。
练习 4:使用 stash
目标:理解 stash 的使用场景。
bash
# 修改文件但不提交
echo "这一行还没写完..." >> README.md
git status # 看到修改
# 突然需要切到其他分支
git stash push -m "README 草稿"
git status # 工作区干净了
# ... 去做别的事 ...
# 回来继续
git stash pop
# 修改回来了,继续写
✅ 检查点:git stash list 为空,修改恢复了。
练习 5:修改提交信息
目标 :掌握 git commit --amend。
bash
# 故意写个错的提交信息
git commit --allow-empty -m "fx: 这是错的"
git log --oneline -1 # 看到错误信息
# 修正
git commit --amend -m "fix: 这是修正后的信息"
git log --oneline -1 # 确认已修正
✅ 检查点:提交信息已更正。
练习 6:合并多个 commit(交互式 rebase)
目标 :使用 git rebase -i 的 squash。
bash
# 连续创建 3 个空提交
git commit --allow-empty -m "WIP: 步骤 1"
git commit --allow-empty -m "WIP: 步骤 2"
git commit --allow-empty -m "WIP: 步骤 3"
# 合并为 1 个
git rebase -i HEAD~3
# 将后两个的 pick 改为 squash (s)
# 保存,编辑合并后的提交信息
✅ 检查点:git log --oneline 显示 3 个提交变成了 1 个。
练习 7:创建 GitHub 仓库并 push
目标:连接本地和远程仓库。
bash
# 1. 在 GitHub 网页上创建新仓库(不要勾选 README)
# 2. 关联远程仓库
git remote add origin git@github.com:你的用户名/仓库名.git
# 3. 推送
git push -u origin main
# 4. 在 GitHub 网页上确认文件已上传
✅ 检查点:GitHub 网页上能看到你的代码。
练习 8:Fork 并提交 PR
目标:完整走一遍开源贡献流程。
bash
# 1. 在 GitHub 上找一个简单的项目(有 good first issue 标签的)
# 2. Fork 该项目
# 3. Clone 你的 fork
git clone git@github.com:你的用户名/项目名.git
cd 项目名
# 4. 创建功能分支
git switch -c fix/small-fix
# 5. 修改、提交、推送
# ... 编辑代码 ...
git commit -am "fix: 描述你做了什么"
git push origin fix/small-fix
# 6. 在 GitHub 上创建 PR
✅ 检查点:PR 页面能正常打开,没有冲突标记。
练习 9:使用 git bisect
目标:二分法定位问题提交。
bash
# 1. 创建一个模拟场景
git switch -c bisect-test
for i in 1 2 3 4 5 6 7 8; do
echo "line $i" >> test.txt
git add test.txt && git commit -m "add line $i"
done
# 2. 在第 5 个提交引入 bug
# (实际场景是你发现最后版本不对,但不知道哪个提交引入的)
# 模拟: echo "bug" >> test.txt 在某个中间提交
# 3. 启动 bisect
git bisect start
git bisect bad HEAD # 最后一个有 bug
git bisect good HEAD~7 # 第一个是好的
# 4. Git 会自动切到中间版本,你来判断
# git bisect good 或 git bisect bad
# 重复直到找到问题提交
# 5. 结束
git bisect reset
✅ 检查点:bisect 能精确找出引入 bug 的提交。
练习 10:从"提交到错误分支"的灾难中恢复
目标:综合运用 reset、stash、cherry-pick。
bash
# 模拟:在 main 分支上做了 2 个提交,本应在 feature 分支上
# 创建场景
git switch main
echo "feature A" > feature-a.txt && git add . && git commit -m "feat: 功能 A(应该在 feature 上)"
echo "feature B" > feature-b.txt && git add . && git commit -m "feat: 功能 B(应该在 feature 上)"
# 修复
# 方法 1:Cherry-pick 法
git switch -c feature/rescue # 从当前提交创建分支
git switch main # 回到 main
git reset --hard HEAD~2 # 撤销 main 上的 2 个提交
# 方法 2:Reset 法
# git log --oneline 记下两个提交的哈希
# git reset --hard HEAD~2
# git switch -c feature/rescue
# git cherry-pick 提交A哈希 提交B哈希
✅ 检查点:两个提交在 feature/rescue 分支上,main 分支回到了之前的状态。
第九部分:速查表
创建与克隆
| 命令 | 说明 |
|---|---|
git init |
初始化本地仓库 |
git clone <url> |
克隆远程仓库 |
基本操作
| 命令 | 说明 |
|---|---|
git status |
查看状态(最常用) |
git add <file> |
添加到暂存区 |
git add -A |
添加所有修改 |
git commit -m "msg" |
提交 |
git commit -a -m "msg" |
跳过 add 直接提交 |
git commit --amend |
修改上次提交 |
git push origin <branch> |
推送到远程 |
git pull |
拉取并合并 |
git fetch |
只拉取不合并 |
分支
| 命令 | 说明 |
|---|---|
git branch |
查看分支 |
git branch <name> |
创建分支 |
git switch <name> |
切换分支 |
git switch -c <name> |
创建并切换 |
git merge <branch> |
合并分支 |
git rebase <branch> |
变基 |
git branch -d <name> |
删除分支 |
撤销
| 命令 | 说明 |
|---|---|
git restore <file> |
丢弃工作区修改 |
git restore --staged <file> |
取消暂存 |
git reset --soft HEAD~1 |
撤销提交,保留修改 |
git reset --hard HEAD~1 |
撤销提交,丢弃修改 |
git revert <commit> |
安全撤销(创建新提交) |
git stash |
暂存工作区 |
git stash pop |
恢复暂存 |
历史
| 命令 | 说明 |
|---|---|
git log --oneline --graph |
图形化历史 |
git diff |
查看未暂存修改 |
git diff --staged |
查看已暂存修改 |
git show <commit> |
查看某次提交详情 |
git blame <file> |
查看每行代码的作者 |
git reflog |
查看操作历史(后悔药) |
远程
| 命令 | 说明 |
|---|---|
git remote -v |
查看远程仓库 |
git remote add <name> <url> |
添加远程 |
git push -u origin <branch> |
首次推送并关联 |
git push --force-with-lease |
安全强制推送 |
附录
附录A:推荐的 .gitconfig
将以下内容保存到 ~/.gitconfig:
ini
[user]
name = 你的名字
email = 你的邮箱@example.com
[init]
defaultBranch = main
[core]
autocrlf = true # Windows; Mac/Linux 用 input
quotepath = false # 正确显示中文
editor = code --wait # 使用 VS Code
[alias]
st = status
co = checkout
br = branch
ci = commit
lg = log --oneline --graph --all
unstage = restore --staged
undo = reset --soft HEAD~1
[pull]
rebase = true # pull 时默认用 rebase
[rerere]
enabled = true # 记住冲突解决方案
附录B:推荐学习资源
交互式学习(强烈推荐):
- Learn Git Branching --- 可视化学习分支操作,游戏化教学
- Oh My Git! --- 卡牌游戏学 Git
官方文档:
- Pro Git(中文版) --- Git 官方推荐书籍,免费在线阅读
- Git 官方文档
GitHub 学习:
- GitHub Skills --- GitHub 官方互动教程
- First Contributions --- 手把手教你做第一个开源贡献
日常使用:
- Oh Shit, Git!?! --- 常见翻车场景及急救方法(有中文版)
- git-tips --- Git 技巧合集
附录C:.gitignore 模板
根据项目类型选择:
Python:
gitignore
__pycache__/
*.py[cod]
*.so
venv/
.env
.venv
dist/
*.egg-info/
Node.js:
gitignore
node_modules/
npm-debug.log*
.env
dist/
.cache/
Java:
gitignore
target/
*.class
*.jar
*.war
.idea/
*.iml
.gradle/
build/
通用(任何项目都推荐加):
gitignore
# 操作系统
.DS_Store # Mac
Thumbs.db # Windows
desktop.ini # Windows
# 编辑器
.vscode/
.idea/
# 环境变量和密钥
.env
.env.local
*.key
*.pem
# 压缩包
*.zip
*.tar.gz
*.7z
最后的话 :Git 是工具,不是目的。不要追求记住所有命令------理解核心概念(三棵树、分支是指针、reflog 是后悔药),把
git status和git help当成习惯,遇到问题先搜后问。写代码时频繁提交、大胆分支,Git 不会让你丢数据。这篇指南你可以收藏起来,作为日常参考手册。祝你编码愉快!