给代码一颗“后悔药”:Git 从入门到工作流的完整修炼手册
PS:如今已经可以通过 ai agent 用自然语言的方式操作 git,以下内容学习熟知即可
序:Git的安装与配置
Windows 用户:
- 访问 Git - Install for Windows
- 下载 64-bit 版本的安装包
- 双击安装,安装过程中保持所有默认选项即可(会有很多页配置,不用改,一直点 Next)
- 安装完成
macOS 用户:
macOS 通常自带 Git
打开终端输入 git --version,如果有输出就不需要安装
如果没有,执行:
# macOS 安装 Git(通过 Xcode 命令行工具)
$ xcode-select --install
弹出的对话框中点击"安装"即可
验证安装:
$ git --version
预期输出:
git version 2.43.0
首次配置(必须):
安装完成后,需要告诉 Git 你是谁(这些信息会记录在每次代码提交中):
# 设置你的名字(用英文,可以是昵称)
$ git config --global user.name "Your Name"
# 设置你的邮箱
$ git config --global user.email "your.email@example.com"
提示:这里的名字和邮箱不需要是真实的,但建议和将来注册 GitHub 的邮箱保持一致
一. Git 简介
1. 什么是Git
Git 是一个分布式版本控制系统
简单理解,Git 就像一颗后悔药,可以帮你记录文件的每一次修改,随时回退到任意历史版本
注意:Git 并非每次都存储完整文件,它会引用没有变化的文件,储存有变化的文件
2. Git 对象模型
Git 的底层是三种对象(blob、tree、commit),存在 .git/objects/ 目录下:
blob(文件内容)
存储文件的具体内容
每个blob只存储文件数据,不包含文件名
tree(目录结构)
存储目录的信息:
- 哪些文件名对应哪些 blob
- 哪些子目录对应哪些 tree
tree 就是一张「文件名 → blob」的映射表
commit(提交)
存储一次提交的全部信息:
- 指向一个 tree(这次提交的项目快照)
- 指向父 commit(上一次提交)
- 作者、时间、提交信息
commit -> tree -> blob
|
parent commit -> tree -> blob
每个对象用 SHA-1 哈希值作为唯一标识(如 a1b2c3d)
这就是为什么 Git 能保证数据完整性——内容变了,哈希就变了
3. Git 的四个区域
3.1 Working Directory(工作区)
实际编辑文件的地方
用编辑器改代码,改的就是工作区的文件
3.2 Staging Area / Index(暂存区)
一个「准备区」
你选择哪些修改要放进下一次提交
git add 就是把文件放进暂存区
3.3 Local Repository(本地仓库)
存储在本地的仓库
提交历史存在这里
git commit 就是把暂存区的内容打包成一个快照,存进本地仓库
3.4 Remote Repository(远程仓库)
服务器上的仓库(如 GitHub)
git push 把本地提交推到远程,git pull 把远程更新拉到本地
暂存区的存在意义:
可以精确控制每次提交包含哪些修改
不用每次都把所有改动一起提交
二. 初始化与配置
1. git init — 初始化仓库
执行git init命令会在当前目录创建一个 .git 隐藏文件夹( Git 仓库的全部内容都在这个文件夹里,之后所有的版本控制操作都在这个目录下进行)
2. git config — 配置用户信息
设置用户名和邮箱:
git config user.name "your name"
git config user.email "your@email.com"
查看当前配置:
git config user.name
git config user.email
三个配置级别:
| 参数 | 作用范围 | 配置文件位置 |
|---|---|---|
--local(默认) | 仅当前仓库 | .git/config |
--global | 当前用户的所有仓库 | ~/.gitconfig |
--system | 所有用户 | /etc/gitconfig |
git config --global user.name "your name"
git config --global user.email "your@email.com"
建议:用
--global设置名字和邮箱,这样每个新仓库都会自动使用
3. 创建文件
使用 Git 前,先创建一些文件
以下是常用的辅助命令:
注意:也可以在工作区右键鼠标点击,选择「创建文件」,双击文件也可以编辑,可通过红绿颜色区分已暂存和未暂存的修改
touch — 创建空文件
touch readme.md
echo + > — 写入内容
echo "hello world" > readme.md
> 会覆盖文件原有内容,如果文件不存在,会自动创建
echo + >> — 追加内容
echo "第二行" >> readme.md
>> 会在文件末尾追加,不影响已有内容
cat — 查看文件内容
cat readme.md
把文件内容打印到终端
注意:这些不是 git 命令,但它可以帮我们准备 git 要管理的文件
三. 基础指令
1. git add — 暂存修改
git add 把文件从工作区移到暂存区
用法:
git add file.txt— 暂存单个文件git add .— 暂存所有修改git add *.js— 暂存所有 .js 文件
暂存区(Index):
Git 特有的概念
是一个「预提交区」——可以选择性地把某些修改放进去,而不是把所有改动一次性提交
注意:
git add不会创建提交,只是把文件放进「准备区」
可以多次 add 后一次性 commit
2. git status — 查看状态
git status 显示工作区和暂存区的当前状态
具体状态:
- 哪些文件已暂存(准备提交)
- 哪些文件已修改但未暂存
- 哪些文件未跟踪(新文件,Git 还不知道它的存在)
3. git diff — 查看差异
git diff 显示文件的具体修改内容(逐行比较两个版本,告诉你改了什么)
常用用法:
git diff --staged— 比较暂存区 vs 上一次 commit,看 add 了什么git diff— 比较工作区 vs 暂存区,看改了但还没 add 的内容
输出格式:
- hello world (旧行,被删除)
+ Hello Git (新行,被添加)
4. git commit — 提交更改
git commit -m "提交信息" 把暂存区的内容打包成一个 commit,存入本地仓库
一次 commit 就是一份项目快照——记录了这一刻所有文件的状态
提交信息 应该简洁描述做了什么:
git commit -m "Add readme file"
每次 commit 都会记录:
改了什么文件、改了什么内容、上一次 commit 是谁
这样就形成了一条完整的历史链
4. git log — 查看提交历史
git log 显示从最新到最早的提交记录
输出格式:
commit a1b2c3d (HEAD -> main)
Author: Your Name <your@email.com>
Date: Mon Jun 2 10:00:00 2025
Add readme
HEAD -> main 表示 HEAD 当前指向 main 分支的最新 commit
HEAD 记录的是「当前在哪个 commit 上」——切换分支时,HEAD 会跟着移动
常用选项:
git log -n 3— 只看最近 3 条git log --oneline— 简洁模式(一行一条)
5. git restore — 恢复文件
git restore 用于恢复文件内容
数据通常在三个地方流动:工作区、暂存区、提交历史(HEAD)
场景 1:丢弃工作区的修改(最常用)
git restore readme.md
把工作区的文件恢复成暂存区里的样子
如果没暂存过,就恢复成最后一次提交的样子
场景 2:取消暂存(撤销 git add)
git restore --staged readme.md
把文件从暂存区撤出来,但不会改变工作区刚写的代码
场景 3:同时丢弃暂存区和工作区的修改
git restore --staged --worktree readme.md
彻底放弃这个文件的所有修改,让它直接回到最后一次提交的状态
场景 4:从历史提交中恢复文件
git restore --source=HEAD~1 readme.md
把上一次提交里的文件,直接拉到现在的工作区里
注:
HEAD代表当前最新提交,HEAD~1代表上一次提交,HEAD~2是上两次,依此类推
6. git checkout Hash
切换到指定的commit
要注意使用该指令时处于分离HEAD状态,最好不要在此基础上修改文件
该指令常用于临时查看历史commit的内容
四. 分支操作
1. git branch — 分支操作
分支是 Git 最强大的功能之一
分支就是一个指向某个 commit 的标记
创建分支只是加个标记,瞬间完成
用法:
git branch— 列出所有分支git branch dev— 创建名为 dev 的新分支git branch -d dev— 删除分支
创建分支时,新分支指向当前 commit
两个分支共享之前的历史,之后各自独立发展
main: A -- B -- C
\
dev: D -- E
2. git checkout — 切换分支
git checkout 切换到指定分支
切换分支时,Git 会:
-
让 HEAD 指向新分支
-
把工作区的文件更新为该分支最新 commit 的内容
创建并切换(一步到位):
git checkout -b dev — 创建 dev 分支并立即切换过去
切换后,工作区会变成 dev 分支的内容
两个分支可以各自独立开发,互不影响
git checkout -b dev Hash — 从指定的commit处创建 dev 分支并立即切换过去
切换后,工作区会变成 dev 分支的内容
两个分支可以各自独立开发,互不影响,且是从指定的commit开始延伸
[!Git 史话:checkout、switch 与 restore]
在早期,
git checkout承担了太多责任,核心逻辑是“把某个东西拿出来放到工作区”这导致了极大的概念混乱:
操作分支:
git checkout dev(切换分支)操作文件:
git checkout -- readme.md(丢弃修改,恢复文件)分家(Git 2.23 引入):
为了让命令见名知意,Git 社区将
checkout的功能一分为二,推出了两个专职的新命令:
git switch:专职负责切换分支git restore:专职负责恢复文件(还顺便接管了以前用reset HEAD来取消暂存的工作)虽然
checkout依然可用,但现代 Git 教程强烈建议你拥抱switch和restore
3. git switch — 切换分支
git switch 是 Git 2.23 引入的新命令,用来替代 git checkout 的分支切换功能
原因:git checkout 职责太多(切换分支、恢复文件、创建分支…),容易混淆
用法:
git switch main— 切换到 main 分支git switch -c feature— 创建并切换到 feature 分支
和 git checkout 的对应关系:
git checkout dev→git switch devgit checkout -b dev→git switch -c dev
4. git merge — 合并分支
git merge 把指定分支的修改合并到当前分支
用法:先切到目标分支(通常是 main),再 merge 其他分支:
git switch main
git merge dev
两种合并方式: 快速合并与三方合并
快进合并(Fast-forward)
前提:main 没有新 commit,dev 在 main 前面
Git 只需把 main 移到 dev 的位置:
之前: main ── A
\
dev B ── C
之后: main ── A ── B ── C
三方合并(3-way merge)
前提:main 和 dev 都有新 commit
Git 找到分叉点,合并两边的修改,生成一个新的合并 commit:
main ── A ── B ── M
/
dev C ── D
M 是合并 commit,它有两个父 commit(B 和 D)。
合并冲突
当两个分支修改了同一个文件的同一位置,Git 无法自动合并,就会产生冲突
冲突标记:
<<<<<<< HEAD
当前分支的内容
=======
要合并的分支的内容
>>>>>>> dev
解决步骤:
-
打开冲突文件,找到
<<<<<<<标记 -
决定保留哪部分(或合并两者)
-
删除冲突标记
-
git add标记为已解决 -
git commit完成合并
冲突是正常现象
Git :“这两个修改我没法自动合并,你来决定”
五. 远程协作
1. SSH Key — 认证远程仓库
要和 GitHub 等远程仓库通信,需要证明你的信息,而SSH Key 是最常用的方式
原理:
- 生成一对密钥:私钥(留在本地)和公钥(上传到 GitHub)
- GitHub 用公钥验证你的身份,你用私钥签名
步骤 1:生成密钥
ssh-keygen -t rsa -C "your@email.com"
一路回车即可
默认生成在 ~/.ssh/id_rsa
步骤 2:复制公钥
cat ~/.ssh/id_rsa.pub
复制输出的整行内容
步骤 3:添加到 GitHub
GitHub → Settings → SSH and GPG keys → New SSH key → 粘贴公钥
步骤 4:测试连接
ssh -T git@github.com
看到 Hi username! 就说明配置成功了
配置好 SSH 后,clone/push/pull 都可以用
git@github.com:用户名/仓库.git格式的 URL,无需每次输入密码
2. 远程仓库与 git push
远程仓库是托管在服务器上的 Git 仓库(如 GitHub)
本地仓库通过 push/pull 和远程仓库同步
添加远程仓库:
git remote add origin git@github.com:user/repo.git
origin 是远程仓库的默认名字,也可以自己取名字
查看已配置的远程仓库:
git remote -v
推送到远程:
git push origin main
把本地 main 分支的 commit 发送到远程
之后在 GitHub 上就能看到代码了
3. 克隆项目
场景:从远程克隆一个项目
3.1 git clone — 克隆仓库
git clone 从远程仓库复制一份完整的项目到本地:
git clone git@github.com:user/repo.git
它会:
-
下载远程仓库的全部内容
-
创建本地仓库和工作区
-
检出默认分支(通常是 main)
-
自动设置远程地址为
origin
克隆完成后,拥有完整的项目历史,和远程仓库一模一样
这就是「分布式」的含义——每个人的本地都是一个完整的仓库
两种 URL 格式:
- HTTPS:
https://github.com/user/repo.git - SSH:
git@github.com:user/repo.git(需要配置 SSH Key)
3.2 git fetch — 获取远程更新
使用 fetch 前,需要先配置远程仓库地址:
git remote add origin git@github.com:user/repo.git
git fetch origin
fetch 从远程下载新的 commit,但不会合并到你的工作区
它只更新本地的远程记录(如 origin/main),让你知道远程有什么新内容,但不改动你的文件
fetch 前: 本地 main → C, 远程 main → E
fetch 后: 本地 main → C, origin/main → E
(你还是在 C,但知道远程有 E 了)
git pull=git fetch+git merge
想精确控制时,可以先 fetch 再决定是否 merge
3.3 git pull — 拉取并合并
使用 pull 前,需要先配置远程仓库地址(如果还没配置过):
git remote add origin git@github.com:user/repo.git
git pull origin main
origin 是远程仓库名,main 是分支名
默认情况下,git pull = git fetch + git merge
pull 先从远程下载 commit,然后在 local repo 里合并分支,最后更新工作区的文件
日常开发中,开始工作前先 git pull 是个好习惯
本地: A -- B -- C
远程: A -- B -- D -- E
pull 后: A -- B -- C -- M (merge commit)
\ /
D -- E
[!扩展:GitHub Pull Request (PR) 的合并方式]
把代码 push 到远程并提起 PR 后,在 GitHub 页面上通常有三种合并方式:
Create a merge commit:
保留所有 commit 历史,并生成一个新的 Merge commit(类似上面的 M)
适合保留完整开发过程Squash and merge:
把你的多个 commit 压缩成一个全新的 commit 加入主分支
主分支历史最干净,适合小功能Rebase and merge:
把你的 commit 逐个复制并重新排列在主分支最前面,没有 merge commit,历史是一条直线
到这里,已经掌握了 Git 的核心工作流:
add → commit → push & pull
这四个命令覆盖了日常开发 90% 的场景
后面的章节是进阶内容
六. 撤销与修正
1. git reset — 重置
git reset 通过移动 HEAD 指针来撤销 commit
有三种模式,区别在于如何处理暂存区和工作区:
| 重置模式 | HEAD | 暂存区 (Index) | 工作区 (Working Directory) | 适用场景 |
|---|---|---|---|---|
--soft(软重置) | 移回旧 commit | 保留变更(放入暂存区) | 不变 | commit 拆分/合并、修正提交信息 |
--mixed(默认) | 移回旧 commit | 重置为旧 commit 状态 | 不变 | 撤销 commit 及 git add,重新整理修改 |
--hard(硬重置) | 移回旧 commit | 强制重置 | 强制重置(未提交修改永久丢失) | 彻底丢弃当前所有改动,回到干净的旧状态 |
注意:
--hard是危险操作,执行前请确认工作区和暂存区中没有需要保留的未提交内容
git reset --soft HEAD~1 # 仅撤销 commit
git reset --mixed HEAD~1 # 撤销 commit 和 add (默认)
git reset --hard HEAD~1 # 彻底回退,抛弃所有修改
误用 --hard 怎么办?
只要没有执行过 git gc,Git 的 reflog 记录了 HEAD 的所有移动历史
1git reflog # 找到误操作前的 commit hash
2git reset --hard <hash> # 恢复到那个状态
记住:Git 几乎不会真正丢失数据,reflog 是你的终极后悔药
2. git revert — 撤销提交
git revert 创建一个新的 commit,内容是「撤销指定 commit 的修改」
和 reset 的区别:
reset是删除历史(改写已有的 commit)revert是新增历史(创建一个反向 commit)
原来: A -- B -- C
revert B: A -- B -- C -- B'
(B' 把 B 的改动反向做了一遍)
黄金法则:
commit 已经 push 到远程并被其他人使用了,用 revert,不用 reset
reset 改写历史会导致其他人的代码出问题
七. 进阶工具
1. git rebase — 变基
git rebase main 把当前分支的 commit 逐个「移植」到 main 的最新 commit 之后
rebase vs merge:
假设在 dev 分支上有两个 commit(D、E),main 上也有新 commit(B、C):
用 merge 合并:
main: A ── B ── C ── M (M 是合并 commit,有两个父 commit)
\ /
dev: D ── E
历史保留了分叉结构,但多了一个 M
用 rebase 合并:
main: A ── B ── C
\
dev: D' ── E' (D' 和 E' 是 D、E 的副本,哈希不同)
历史变成一条直线,没有合并 commit
rebase 的本质:把 commit 摘下来,接到目标分支的最新位置,像「嫁接」一样
黄金法则:
不要 rebase 已经 push 到远程的 commit!
rebase 会改写 commit 的哈希值,导致其他人的代码出问题
只在 push 之前 rebase 本地 commit
2. git stash — 临时保存
git stash 把当前工作区和暂存区的修改「藏起来」,让工作区恢复干净
场景: feature 分支开发到一半,突然需要切到 main 修复 bug,但修改还没法 commit,这时候 stash 就派上用场了
用法:
git stash— 保存当前修改git stash list— 查看所有 stashgit stash pop— 恢复最近一次 stash 并删除记录git stash apply— 恢复但保留记录
git stash # 藏起来
git switch main # 切到 main 修 bug
git switch feature # 切回来
git stash pop # 恢复之前的工作
3. git worktree — 多开工作区
正常情况下,一个 Git 仓库只能同时检出一个分支的代码在工作区里
但 git worktree 允许你为同一个本地仓库创建多个并行的工作目录
痛点场景:
在 feature 分支写了大量代码,本地环境正跑着
突然老板让你立刻切到 main 修紧急 Bug
- 直接切?有大量未提交修改,Git 会阻止
- 用 stash 藏起来切?切分支会导致项目依赖或编译产物被冲刷,切来切去很痛苦
Worktree 的完美解法:
保持当前目录一动不动,直接在旁边开辟一个新目录修 Bug!它们底层共享同一个 Git 数据库
# 在当前仓库外创建一个名为 hotfix 的新目录,并检出 main 分支
git worktree add ../hotfix main
# 修完 Bug 提交后,清理掉这个临时目录
git worktree remove ../hotfix
极速、省空间,而且彻底隔离了开发环境,保护了开发时的心流
4. git cherry-pick — 拣选提交
git cherry-pick 把某个 commit 的改动内容(diff) 提取出来,在当前分支重新应用一遍
场景:在 dev 分支修了一个 bug(commit D),但不想合并整个 dev,只想把这个修复拿到 main 上
dev: A ── B ── C ── D (D 改了 login.py 的第 10 行)
main: A ── B
git switch main
git cherry-pick D
main: A ── B ── D' (D' 也改了 login.py 的第 10 行)
cherry-pick 不是复制文件快照,而是提取「改了什么」,在当前位置重新改一遍
所以 D’ 和 D 的哈希不同,但改动内容相同
注意:
这是「复制改动」不是「移动」
dev 上的 D 仍然存在
5. git tag — 标签
标签用于标记发布版本(v1.0, v2.0, …),方便以后快速找到这个版本
标签和分支类似,都是指向 commit 的标记。区别是:
- 分支会随着新 commit 往前移动
- 标签永远固定在创建时的 commit 上
git tag v1.0 # 给当前 commit 打标签
git tag # 列出所有标签
git show v1.0 # 查看标签对应的 commit
git tag -d v1.0 # 删除标签
轻量标签(上面用的)只是一个标记
还有一种附注标签(git tag -a v1.0 -m "message"),包含作者信息和说明,适合正式发布
6. git rm — 删除文件
git rm 从工作区和暂存区同时删除文件,并记录这次删除
之后 commit 即可
git rm readme.md # 删除文件 + 暂存删除操作
git commit -m "Remove readme"
和手动删除的区别:
- 手动删除文件后,需要
git add告诉 Git 删了什么 git rm一步到位:删文件 + 暂存删除操作
如果只想从 Git 跟踪中移除(保留本地文件):
git rm --cached <file>
适用场景:不小心把 .env 之类的文件 add 进去了,想从跟踪中移除
7. git blame — 追溯修改
git blame 逐行显示文件的最后修改者和 commit
输出格式:
a1b2c3d (Alice 2025-01-15) def login():
b2c3d4e (Bob 2025-02-01) check_token()
每行前面显示:commit 哈希、作者、日期、内容
用途:排查某行代码是谁改的、为什么改。配合 git log 和 git show 可以追溯完整上下文
git blame login.py # 查看整个文件
git blame -L 10,20 login.py # 只看第 10-20 行
八. 工作流
1. 日常开发流程
一个典型的 Git 工作日:
# 1. 拉取最新代码
git pull origin main
# 2. 创建功能分支
git switch -c feature/login
# 3. 开发、提交
git add .
git commit -m "Add login form"
# 4. 推送到远程
git push origin feature/login
# 5. 创建 Pull Request(在 GitHub 上)
# 6. 代码审查后合并到 main
# 7. 切回 main,拉取最新
git switch main
git pull origin main
# 8. 删除功能分支
git branch -d feature/login
核心循环就是:修改 → add → commit → push → PR → merge
2. Git Flow 工作流
Git Flow 是一种流行的分支管理策略:
- main — 永远是生产环境的代码
- develop — 开发主线
- feature/* — 功能分支,从 develop 分出
- release/* — 发布准备分支
- hotfix/* — 紧急修复分支
main: A --------- M --------- R
\ ^ ^
dev: D -- F -- D -- F --- D
^ ^
feature: f1 f2
小团队可能不需要这么复杂
简单的策略:
- main 保持可部署
- 每个功能一个分支
- 通过 PR 合并
选择适合你团队的工作流,不要为了流程而流程
结语:
学 Git 并不难 ,但它却是每一位开发者不可或缺的核心基石
尤其在 AI 时代,当自然语言操作逐渐普及,理解 Git 的底层原理与工作流反而愈发关键——它不仅是代码的“后悔药”,更是人机协作、版本追溯与团队协同的信任基础
工具会迭代,但对版本控制的深刻认知,永远是高效开发的底气
愿这份笔记成为你随时可查阅的地图,在每一次 commit 与 merge 中,走得更稳、更远
更多推荐



所有评论(0)