Git零基础完全掌握指南

Git 零基础完全掌握指南

从"完全不会"到"独立解决问题",一份覆盖命令、实战、排错、面试的 Git 自学文档。


目录


第一部分:入门概念

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:

  1. git-scm.com 下载安装包
  2. 一路默认安装(推荐勾选 "Git Bash Here",它会给你一个 Linux 风格的终端)
  3. 验证:打开 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 做了三件事:

  1. 下载所有文件
  2. 下载完整的提交历史
  3. 自动创建名为 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 switchgit 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

解决步骤:

  1. 打开冲突文件,找到 <<<<<<<>>>>>>> 标记
  2. 决定保留哪个版本(或合并两者),删除标记
  3. git add 文件名(标记冲突已解决)
  4. 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 会自动展示在你的个人主页:

  1. 新建仓库,名称 = 你的 GitHub 用户名
  2. 勾选 "Add a README file"
  3. 编辑 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 mergegit rebase 的区别?什么时候用哪个?

  • merge:创建一个合并提交,保留完整的分支历史。适合公共分支、多人协作。
  • rebase:将当前分支的提交"重放"到目标分支顶端,历史变成一条直线。适合个人分支整理。
  • 选择 :本地未 push 的提交用 rebase 整理干净,公共分支用 merge 保留历史。绝不对已 push 的提交做 rebase。

7.2 git resetgit revert 的区别?

  • reset:移动 HEAD 指针,可以改写历史。适合撤销还没 push 的提交。
  • revert:创建一个新的提交来撤销之前的修改,不改写历史。适合已 push 的提交。
  • 关键区别:revert 是安全的(不破坏历史),reset 可以彻底删除提交。

7.3 git fetchgit 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 HEADORIG_HEADFETCH_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:第一次提交

目标 :掌握 initaddcommitlog 流程。

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:推荐学习资源

交互式学习(强烈推荐):

官方文档:

GitHub 学习:

日常使用:

附录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 statusgit help 当成习惯,遇到问题先搜后问。写代码时频繁提交、大胆分支,Git 不会让你丢数据。

这篇指南你可以收藏起来,作为日常参考手册。祝你编码愉快!