1. 引言
刚接触 Git 时,git status 的输出总让人困惑。“Changes not staged for commit” 和 “Changes to be committed” 到底有什么区别?为什么修改一个文件要先后执行 add 和 commit 两条命令才能保存?更别提 push、pull、fetch 这些词混在一起时,脑子里只剩下一串靠记忆敲出来的命令。
这些困惑的根源在于没有理清 Git 的四个核心区域:工作区、暂存区、本地仓库、远程仓库。一旦搞懂数据在这四个区域之间如何流动,所有常用命令就变成了简单的搬运操作,不再需要背命令,只需要知道“当前文件在哪”以及“要去哪”。
2. 四个区域:一个思维模型
在敲任何命令之前,先在脑海里建立一张地图。
-
工作区(Working Directory)
电脑上实际看到的项目文件夹,正在编辑代码的地方。可以理解为草稿纸,可以随意涂改,但这些改动还没有被 Git 记录。 -
暂存区(Staging Area / Index)
一个临时的清单,记录下一次要提交哪些文件的哪些改动。好比把草稿纸上的内容挑选后写到待办卡片上,随时可以修改卡片内容,但还没正式归档。 -
本地仓库(Local Repository)
存放在项目根目录.git文件夹里的完整版本历史。每执行一次commit,就等于把暂存区当前的内容拍一张快照,永久存放到本地仓库中。 -
远程仓库(Remote Repository)
托管在服务器(例如 GitHub、GitLab、Gitee)上的版本库,用于多人协作或备份。push把本地仓库的提交发送到远程,pull/fetch把远程的更新拉回本地。
数据在四个区域之间是单向或双向流动的:
- 工作区 → 暂存区:
git add - 暂存区 → 本地仓库:
git commit - 本地仓库 → 远程仓库:
git push - 远程仓库 → 本地仓库(且进一步可合并到工作区):
git fetch/git pull
3. 配置用户身份
在执行第一次 git commit 之前,需要先设置用户名和邮箱:
3.1 全局配置
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
--global 表示对当前电脑的所有 Git 仓库生效。如果某个仓库需要单独使用不同身份,去掉 --global 并在该仓库内执行。
3.2 验证配置
git config --global --list | grep user
会输出包含刚才设置的名字和邮箱。
这一步只需要执行一次。配置信息会写入
~/.gitconfig文件。
4. 创建示例项目:git-test
为了连贯地演示后续所有操作,这里创建一个名为 git-test 的项目,并推送到远程仓库。
# 创建项目目录并进入
mkdir git-test
cd git-test
# 初始化本地仓库
git init

执行 git init 后,当前目录下会出现一个 .git 子目录,这就是本地仓库的存储位置。
ls -la | grep .git

创建第一个文件并提交
echo "Hello Git" > test.txt
git add test.txt
git commit -m "Initial commit: add test.txt"

关联远程仓库并推送,先在 GitHub / GitLab / Gitee 上创建一个空白仓库(仓库名也设为 git-test,不勾选 README),这里在 GitHub 上进行演示:

# 添加远程仓库地址(刚刚创建的仓库的地址)
# origin 是远程仓库的别名(默认约定名称,可自定义但通常用 origin)
git remote add origin https://github.com/username/git-test.git
# 查看已配置的远程仓库
git remote -v
# 推送并设置上游分支
# 注意:Git 旧版默认主分支为 master,新版平台统一改为 main,分支名不一致会导致推送失败。使用 git branch 查看分支。
# 比如说,远程仓库是 main,本地是 master 时,需要将本地主分支名从 master 修改为 main,适配远程仓库分支名。
# 修改命令为 git branch -m master main
git push -u origin main

注意:如果使用 HTTPS 协议,第一次推送需要输入用户名和个人访问令牌。
验证: 刷新远程仓库页面,应能看到 test.txt 文件和 Initial commit 记录;或执行 git status 显示 up to date with 'origin/main'。

注意:
git clone自动完成git init+remote add+ 拉取历史;而git init用于本地新建仓库,需手动关联远程和推送。两种方式最终都会得到一个包含.git目录并关联了远程的本地仓库。
至此,一个完整的本地 ↔ 远程仓库已就绪。后续所有操作都会基于 git-test 项目进行。
5. 日常修改与提交:工作区 → 暂存区 → 本地仓库
完成克隆或创建仓库后,进入日常开发循环:修改代码 → 提交 → 推送。这一节覆盖了提交阶段的两个核心步骤:add 和 commit。
5.1 第一步始终是 git status
任何时候,如果不确定当前状态,先执行:
git status
输出会分成三类,清晰告诉 Git 目前看到的变更:
- Untracked files:工作区存在新文件,Git 从未跟踪过。
- Changes not staged for commit:已跟踪的文件被修改了,但修改还在工作区,没有进入暂存区。
- Changes to be committed:已经
add到暂存区,等待commit。
验证:
在 git-test 目录下,新建一个 README.md 文件后,git status 应该显示红色的 README.md 归在 “Untracked files” 下。
touch README.md
git status
输出结果:
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)

5.2 git add(工作区 → 暂存区)
add 的作用是将工作区的变更(新增、修改、删除)复制到暂存区。为什么要单独设一个暂存区? 因为现实中一次提交可能需要包含多个文件的改动,甚至只提交一个文件中的部分修改。暂存区允许分批准备,而不是把工作区所有未完成的内容一股脑的提交。
常用的 add 形式:
| 命令 | 作用 |
|---|---|
git add README.md |
只添加指定文件 |
git add . |
添加当前目录下所有新文件及修改(不包括删除) |
git add -A |
添加所有变更(新增、修改、删除),这个命令推荐用于提交前的统一操作 |
git add -p |
交互式选择文件中的部分代码块(hunk)加入暂存区 |
git add -u |
只添加已跟踪文件的修改(包括修改和删除),不包含未跟踪的新文件 |
实践:
# 添加单个文件
git add README.md
# 验证
git status
添加 README.md 后大概会输出这样的结果:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: README.md

可以看到,文件从 “Untracked files” 变成 “Changes to be committed”,颜色从红色变为绿色(前提是终端支持彩色输出)。这说明这个文件已经进入了暂存区。
注意:即使 add 之后修改同一个文件,工作区的新修改仍然是 “Changes not staged”,需要再次 add 才能再把修改放入暂存区。
5.3 git commit – 暂存区 → 本地仓库
commit 把暂存区当前的所有内容打包成一个快照,存入本地仓库。每个提交会生成一个 40 位的 commit ID(SHA‑1),同时记录作者、时间戳、父提交信息。
基本写法:
# 单行提交信息
git commit -m "添加 README.md"
# 多行提交信息(会打开默认编辑器)
git commit
补充:
-a选项:
git commit -a -m "xxxxx"会自动将所有已跟踪文件的修改添加到暂存区(这相当于先执行了git add -u),然后提交。需要注意的是:它不会包含未跟踪的新文件(untracked files)。也就是说,对于只是修改已有文件的情况,-a可以省去手动add的步骤。
验证:
git log -1 # -1 表示只显示最近一条提交记录
大概会输出这样的结果:
commit a1b2c3d4e5f6... (HEAD -> main)
Author: YourName <[email protected]>
Date: Fri Jun 13 03:23:00 2026 +0800
添加 README.md

会显示一个 commit ID、作者、日期和提交信息,这说明本地仓库已经记录了这次提交。HEAD -> main 表示当前分支 main 指向了这个新提交。
5.4 完整实践:修改 → add → commit
# 创建 script.sh 并写入内容
echo 'echo "Hello Git"' > script.sh
# 查看状态 – 应显示 "Changes not staged"
git status
# 添加到暂存区
git add script.sh
# 再次查看 – 应显示 "Changes to be committed"
git status
# 提交到本地仓库
git commit -m "添加 script.sh"
# 验证
git log --oneline -1 # --oneline 将每条提交压缩为一行,-1 只显示最近一条

6. 推送到远程:本地仓库 → 远程仓库
本地提交完成后,需要同步到远程仓库才能与他人共享或作为备份。
6.1 git push 基本格式
git push <远程名> <本地分支名>:<远程分支名>
最常见的情况:本地 main 分支推送到远程的 main 分支,且远程名是 origin。
git push origin main:main
如果本地和远程分支名称相同,可以简写为:
git push origin main
首次推送时设置上游(upstream):
git push -u origin main
-u 选项将本地 main 分支与远程 origin/main 关联。关联之后,后续只需执行 git push(不加任何参数)就会自动推送到关联的远程分支。

6.2 验证推送成功
git status
预期输出:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean

关键字段是 up to date with 'origin/main',表示本地和远程的提交历史完全一致。
也可以直接在网页上打开远程仓库,查看提交记录是否包含刚才的 commit。
6.3 常见错误:远程有新提交
如果推送时出现类似下面的错误:
! [rejected] main -> main (fetch first)
error: failed to push some refs to '...'
原因:在本地提交之前,远程已经有别人推送的新提交。Git 拒绝直接覆盖别人的提交。
解决方法: 先拉取远程更新,合并到本地,再推送。
git pull origin main # 拉取并合并
git push # 重新推送
7. 拉取远程更新:远程仓库 → 工作区
多人协作时,需要将别人推送到远程的提交同步到本地。这里有两个容易混淆的命令:fetch 和 pull。
7.1 fetch vs pull 的区别
git fetch:只从远程仓库下载最新的提交到本地仓库的“远程分支”上(例如origin/main)。它不会改变当前工作区的文件,也不会自动合并到当前分支。相当于只更新了本地仓库中关于远程状态的快照。git pull:等于git fetch+git merge(或rebase)。它既下载更新,又立即将远程分支合并到当前分支,修改工作区文件。
建议: 在对变更内容不确定时,先 fetch 再检查差异,最后手动 merge。这样可以避免自动合并带来的意外冲突或混乱。
git merge 与 git rebase 的区别:
git pull 默认使用 merge 方式合并。另一种常见方式是 git pull --rebase,其行为是先将本地提交“变基”到远程最新提交之后,产生线性的提交历史,而不是产生一个合并节点。
- merge:保留分支历史,但会产生一个额外的合并提交(除非快进合并)。
- rebase:重写本地提交的基底,历史更整洁,但不推荐在公共分支上对已推送的提交进行 rebase。
7.2 安全的拉取流程
为了方便演示远程发生了更新,可以手动在远程仓库修改某个文件的内容,这里我修改了 test.txt 文件。
# 1. 下载远程更新(不修改工作区)
git fetch origin
# 2. 查看远程比本地多了哪些提交
git log HEAD..origin/main --oneline
# 3. 查看具体内容差异
git diff HEAD origin/main
# 4. 确认无误后,合并到当前分支
git merge origin/main
验证合并后的状态:
git log --oneline --graph --all
# --oneline: 每条提交压缩为一行
# --graph: 用 ASCII 图形显示分支分叉和合并结构
# --all: 显示所有分支(包括远程分支)的提交记录

7.3 合并冲突的简单处理
当本地和远程对同一个文件的同一区域都做了修改,并且双方都已提交时,git merge 会产生冲突,Git 会在文件中用特殊标记标出冲突区域。
注意:如果本地存在未提交的修改,Git 会拒绝合并,
这是因为 Git 不想意外覆盖工作区尚未保存的内容。合并前需先
git add+git commit或git stash暂存。
为了方便演示,我手动制造了冲突,分别在远程仓库和合作区对 test.txt 进行修改,分别新增一行内容为 CONFLICT-1 和 CONFLICT-2,然后提交修改。
先在远程仓库(GitHub 网页)修改
test.txt,末尾新增CONFLICT-1并提交。然后在本地仓库执行echo "CONFLICT-2" >> test.txt。
git add test.txt
git commit -m "本地修改:添加 CONFLICT-2"
拉取远程更新并尝试合并:
git fetch origin
git merge origin/main
此时 Git 无法自动合并,会输出:
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.
查看冲突标记:打开 test.txt,内容会变成:
Hello Git
Update
<<<<<<< HEAD
CONFLICT-2
=======
CONFLICT-1
>>>>>>> origin/main
<<<<<<< HEAD到=======之间是本地当前分支的内容(CONFLICT-2)。=======到>>>>>>> origin/main之间是远程分支的内容(CONFLICT-1)。
解决冲突:手动编辑文件,删除 <<<<<<< HEAD、=======、>>>>>>> origin/main 标记,保留最终需要的内容(两行都保留,或者只留其一)。假设最终内容为:
Hello Git
Update
CONFLICT-2
CONFLICT-1
标记冲突已解决并完成合并:
git add test.txt
git commit -m "merge: 解决 test.txt 冲突"
推送合并结果:
git push origin main
验证:git log --oneline --graph --all 会显示一个合并提交。

冲突的详细策略这里不做深入展开。关键是要记住:冲突不是错误,只是 Git 无法自动决定保留哪个版本,需要人工介入。
8. 分支操作
分支是 Git 的强大特性,但在日常使用中最常接触到的只有创建、切换、合并、删除。
分支允许在不影响主线的同时开发新功能。以下基于 git-test 仓库,从 main 分支出发,创建一个 add-contributing 分支,添加 CONTRIBUTING.md 文件后再合并回 main。
8.1 查看当前分支
git branch

8.2 创建新分支
git branch add-contributing
# 验证创建
git branch
这条命令只在 .git 里创建一个新指针(指向当前所在的提交),工作区和当前分支不变。

8.3 切换到新分支
旧命令:git checkout add-contributing
新命令(Git 2.23+):
git switch add-contributing
切换后,工作区的文件会变成目标分支最新提交的快照。验证当前分支:
git branch

也可以创建并切换在一步里完成:
git switch -c another-branch这条命令的作用是:创建 another-branch 并立即切换
8.4 在新分支上做修改并提交
# 创建 CONTRIBUTING.md 文件
echo "Contribution Test" > CONTRIBUTING.md
# 添加到暂存区并提交
git add CONTRIBUTING.md
git commit -m "docs: 在 add-contributing 分支添加贡献测试文档"
验证提交历史:
git log --oneline -1

8.5 切回 main 分支并查看文件
git switch main
ls -la CONTRIBUTING.md
此时 CONTRIBUTING.md 不存在,因为 main 分支尚未包含该提交。

8.6 合并分支
将其他分支的改动合并到当前分支,有两种常见情况:
8.6.1 快进合并(fast‑forward)
触发条件: 当前分支(如 main)在创建特性分支后没有产生新提交,而特性分支在其基础上新增了提交。此时 Git 只需将当前分支的指针直接向前移动到特性分支的最新提交,不会产生额外的合并提交。
实践:
# 当前在 main 分支,准备合并 add-contributing
git merge add-contributing
输出示例:
Updating a1b2c3d..e4f5g6h
Fast-forward
CONTRIBUTING.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 CONTRIBUTING.md
验证:
ls -la CONTRIBUTING.md # 文件已出现
git log --oneline -1 # 提交历史是一条直线,没有合并节点

8.6.2 三方合并(three‑way merge)
触发条件: 当前分支和待合并分支都产生了新提交(即历史发生分叉)。Git 会自动创建一个新的合并提交,将两个分支的修改整合在一起。
模拟一个三方合并的场景:
# 从 main 创建第二个特性分支
git switch -c feature-log
echo "log feature" > log.txt
git add log.txt
git commit -m "feat: 添加 log 功能"
# 切回 main 并也做一次提交
git switch main
echo "main update" >> test.txt
git add test.txt
git commit -m "update: 在 main 上修改 test.txt"
# 此时两个分支分叉,合并会产生三方合并
git merge feature-log
Git 会弹出编辑器让输入合并提交信息(默认已生成 Merge branch 'feature-log'),保存后合并完成。
查看合并后的历史:
git log --oneline --graph --all
# --oneline: 压缩为一行
# --graph: ASCII 图形显示分支结构
# --all: 显示所有分支
输出中会看到一个带有两条父提交的合并节点。

三方合并是多人协作中最常见的合并方式。如果两个分支修改了同一文件的同一区域,则会产生冲突,处理方式见 7.3 节。
8.7 删除已合并的分支
git branch -d add-contributing
-d 选项会安全删除已合并的分支。删除后再查看分支列表:
git branch

8.8 强制删除未合并的分支
如果创建一个分支后提交了改动,但没有合并就试图删除,git branch -d 会拒绝。此时可使用 -D 强制删除(需要慎用,这样会丢失未合并的提交)。
git switch -c temp-branch
echo "temporary" > temp.txt
git add temp.txt
git commit -m "temp commit"
git switch main
git branch -d temp-branch # 报错:The branch 'temp-branch' is not fully merged.
git branch -D temp-branch # 强制删除,提交丢失

实际使用中,建议只删除已经合并到主线的分支,避免丢失工作成果。
9. 撤销操作(三个常用场景)
日常使用中难免需要撤销修改,但是撤销时首先要想清楚:要撤销的数据当前位于哪个区域?目标是想退回到哪个区域的状态?
接下来将在 git-test 仓库中,按“工作区 → 暂存区 → 本地提交 → 已推送”的顺序,演示如何安全撤回。
开始前需要确保
git-test工作区干净(git status显示nothing to commit, working tree clean)。
9.1 场景一:撤销工作区的修改(尚未 add)
操作:修改 test.txt,但还没加到暂存区,想放弃本次修改,恢复到上次提交的状态。
# 先往 test.txt 追加一行错误内容
echo "This is a wrong line" >> test.txt
# 确认修改在工作区(Changes not staged)
git status

撤销命令:
git restore test.txt
# 或旧版语法:git checkout -- test.txt
验证:
cat test.txt # 错误行已消失
git status # 工作区干净

注意:
git restore会直接覆盖工作区文件,无法恢复(除非有编辑器本地历史)。执行前可先用git diff确认要丢弃的内容。
9.2 场景二:撤销暂存区的修改(已经 add,未 commit)
操作:修改 test.txt,执行了 git add,但发现修改有问题,想把它从暂存区移回工作区(保留文件当前内容)。
# 再次添加一行,这次假装写对了内容
echo "Another correct line" >> test.txt
# 误操作 add 到了暂存区
git add test.txt
git status # 显示 Changes to be committed

撤销命令(只清除暂存区,不改变工作区文件):
git restore --staged test.txt
验证:
git status # test.txt 回到 "Changes not staged"
cat test.txt # 文件内容仍在(刚才添加的行还在)

此时可以修改文件内容再重新 add,或者直接用 git restore test.txt 彻底丢弃工作区改动。
9.3 场景三:撤销本地提交(已经 commit,未 push)
操作:提交了一次,但发现提交信息写错或漏了文件,想在本地撤回这次提交。
使用 git reset 移动当前分支的 HEAD 指针。三个主要选项:
| 选项 | HEAD 位置 | 暂存区 | 工作区 |
|---|---|---|---|
--soft |
后退到上一个提交 | 保留原提交的内容 | 不变 |
--mixed(默认) |
后退到上一个提交 | 清空(变更回到工作区) | 不变 |
--hard |
后退到上一个提交 | 清空 | 强制同步(会丢失未提交修改) |
先制造一个需要撤回的提交:
echo "Temporary commit content" >> test.txt
git add test.txt
git commit -m "临时提交:需要撤回的示例"
git log --oneline -1

9.3.1 用 --soft 保留暂存区(只是撤回提交动作)
git reset --soft HEAD~1 # HEAD~1 表示前一个提交
git status # 暂存区仍保留 test.txt 的修改
git log --oneline -1 # 刚才的提交已消失,HEAD 指向上一提交

此时可以重新 git commit -m "新信息" 修正提交信息。
9.3.2 用 --mixed(默认)撤回提交并清空暂存区
重新制造同一个临时提交(确保实验环境一致):
echo "Temporary commit content" >> test.txt # 如果文件已有该行,可跳过
git add test.txt
git commit -m "临时提交:需要撤回的示例"
# 执行 mixed 撤销
git reset --mixed HEAD~1 # 等同于 git reset HEAD~1
git status # 修改回到工作区(Changes not staged)

9.3.3 用 --hard 彻底丢弃(危险)
再次制造临时提交,然后执行:
# 下面命令会丢失工作区和暂存区所有未提交的修改
git reset --hard HEAD~1
执行后 git status 显示干净,test.txt 中临时添加的行完全消失。
警告:
git reset --hard不可恢复。执行前务必git status确认没有需要保留的内容。
9.4 场景四:撤销已经 push 的提交(不推荐 reset)
如果提交已经推送到远程,并且其他人可能已经基于它工作,不要使用 git reset 再 push --force,这会强制改写历史,导致协作者的仓库混乱。正确做法是使用 git revert,它会生成一次反向提交,保留原有历史。
模拟一个已推送的错误提交并撤销:
首先,确保当前工作区干净,并创建一个新的提交推送到远程(模拟错误提交):
# 在 test.txt 末尾添加一行错误内容
echo "This is a wrong commit" >> test.txt
# 提交并推送到远程
git add test.txt
git commit -m "错误提交:需要撤销的示例"
git push origin main
查看提交历史,复制输出中最左边那串短哈希
git log --oneline -3
撤销这个错误提交:
git revert a1b2c3d # 替换为实际的 commit ID
Git 会打开编辑器让你输入撤销提交的信息(默认已生成类似 Revert "错误提交:需要撤销的示例"),保存后退出。
验证:
git log --oneline -3
输出中会多一个 Revert "错误提交:需要撤销的示例" 的提交,而之前的错误提交依然保留在历史中。
cat test.txt # 错误内容已被删除,文件恢复到错误提交前的状态
推送到远程:
git push origin main
此时远程仓库也完成了撤销,协作者拉取时不会遇到历史冲突。

revertvsreset的选择:
- 如果提交已经推送且可能被他人拉取 → 用
git revert。revert是安全的协作方式,不会改写历史,其他人 pull 后能自动合并。- 如果提交仅存在于本地,未推送 → 可以用
git reset自由调整。
10. 结语
最后回顾一下四个区域的数据流向:
- 工作区 → 暂存区:
git add - 暂存区 → 本地仓库:
git commit - 本地仓库 → 远程仓库:
git push - 远程仓库 → 本地仓库:
git fetch/git pull - 本地仓库 → 工作区(反向恢复):
git restore/git checkout
遇到不确定的操作时,先执行 git status,看清楚当前文件在哪个区域,再思考想要把它搬到哪个区域。