AI摘要
此内容由 AI 根据文章正文生成,并已由人工审核
本文通过一个真实的博客项目,系统讲解了Git版本控制的核心概念与实战操作。内容涵盖工作区、暂存区、本地仓库和远程仓库四大区域的关系,详细演示了分支管理、冲突解决、代码撤销、团队协作、发布流程及常见问题排查。文章强调实践导向,提供了从基础命令到高级技巧的完整指南,帮助读者建立清晰的Git操作思维,在代码出错时能准确判断状态并安全恢复。|
不只讲 add、commit、push。我们用一个真实的博客项目,把分支、冲突、撤销、协作、发布和排错这套东西走一遍。
你改了两个小时登录逻辑,刷新页面以后项目突然跑不起来。
你记得自己动过接口、改过一个组件,还顺手整理了配置文件;但具体改了哪里,已经想不清了。Ctrl+Z 早就回不到两个小时前,旧代码也被覆盖了。你现在最想要的不是“把项目写完”,而是回到那个还能运行的版本。
Git 处理的正是这件事:把代码变化记录下来,让你能看清现在改了什么、把一部分修改先收起来、回到过去某个可靠状态,并和其他人交换这些记录。
本文始终使用一个博客项目:
my-blog/
├── apps/
│ ├── web/ # Nuxt 前端
│ └── api/ # FastAPI 后端
├── packages/
├── README.md
├── package.json
└── .gitignore你会在其中完成登录、文章搜索、线上 hotfix 和发布。命令很多,但真正需要形成条件反射的只有一件事:
不知道该怎么办时,先运行 git status;动手回退前,先看 git diff 和 git log。
先把几个概念说清
Git 是什么,GitHub 又是什么
Git 是安装在你电脑上的版本控制工具。即使断网、没有 GitHub,它也能创建仓库、提交历史、切换分支和恢复文件。可以把它当成代码的本地存档系统。
GitHub 是托管 Git 仓库和协作流程的网站。它保存远程副本,提供 Pull Request、代码审查、Issue 和权限管理。GitLab、Gitee 也做类似的事。
下面的类比只帮助理解,不是严格定义:
Git ≈ 相机与相册软件,负责在本地拍照、管理照片版本
GitHub ≈ 云相册,负责备份、共享和多人协作Git 最关键的四个区域
绝大多数初学者的困惑,都来自没有区分“文件改了”“文件准备提交”和“已经提交”。

工作区:你正在编辑的真实文件。保存代码后,修改先出现在这里。
暂存区:这次打算提交的修改清单。git add 是把工作区中的一部分变化放进这里。
本地仓库:你电脑里的 Git 历史。git commit 会在这里留下一个提交。
远程仓库:GitHub/GitLab 上的仓库。git push 才会把本地提交传上去。
它们之间的主路径是:
工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程仓库所以,git add 不是上传,commit 也不是上传。commit 后断网也照样能查看和恢复本地历史。
安装、身份配置与第一个仓库
安装后先确认:
git --versionmacOS 可通过 Xcode Command Line Tools 或 Homebrew 安装;Windows 安装 Git for Windows;Linux 用系统包管理器即可。安装步骤不复杂,真正值得配置的是你的提交身份:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global --list--global 表示写入当前用户的全局配置,之后所有新仓库默认都用它。user.name 和 user.email 会写进提交记录;协作时最好使用与 GitHub 账号关联的邮箱。
git config --list 会同时显示全局、系统和当前仓库的配置。当前仓库里的配置优先级更高,适合工作账号和个人账号并存的情况:
git config user.email "work@example.com"进入项目并初始化:
cd my-blog
git init
ls -lagit init 会创建 .git 目录。提交历史、分支指向、暂存区等 Git 自己的资料都在里面;它才是“这个目录是 Git 仓库”的原因。平时不要手动改 .git,排查问题时用 Git 命令读它。
先写好 .gitignore
这个文件告诉 Git:哪些未被追踪的文件不要加入版本控制。一个 Nuxt + Python 项目常见写法:
node_modules/
.nuxt/
.output/
dist/
.env
.env.local
__pycache__/
*.pyc
.venv/
venv/
.DS_Store
logs/
*.lognode_modules、Python 虚拟环境和构建产物都能从依赖声明重新生成,提交它们只会让仓库膨胀。日志没有必要共享。最重要的是 .env:它经常含数据库地址、JWT Secret、API Key、SMTP 密码或 OAuth Secret,提交后即使删除,敏感内容通常仍在历史里。
已经被 Git 追踪的文件,不会因为后来写进 .gitignore 就自动消失。下面命令只从 Git 的追踪清单移除,保留你磁盘上的 .env:
git rm --cached .env
git commit -m "chore: stop tracking local env file"目录同理:
git rm -r --cached some-directory--cached 的意思是只改暂存区/索引,不删除工作区文件。没有它,git rm 会把本地文件也删掉。
每天都会用:status、diff、add、commit
git status 是你的仪表盘
刚初始化时,先创建 README 和 .gitignore:
git status可能看到:
Untracked files:
README.md
.gitignoreuntracked 表示文件存在,但 Git 还没开始记录它。执行:
git add README.md .gitignore
git status这时它们会在 Changes to be committed 下,叫 staged,也就是“准备提交”。提交以后状态会是:
nothing to commit, working tree cleanmodified 是一个已追踪文件在工作区被改过,但还没 git add。日常更推荐:
git status -sb-s 是短格式,-b 顺便显示分支和与远程的关系。写代码切换任务前看一眼,足够避免很多事故。
先看变化,再决定 add 什么
假设你完成了博客项目的基础说明:
git add README.md
git add apps/web
git add .三者范围不同:第一条只加入一个文件,第二条加入目录里的变化,最后一条加入当前目录下所有未被忽略的变化。git add . 方便,但不该成为肌肉记忆。
例如你修了登录 Bug,又顺手改了 Header 样式和 README。若直接 git add . 再提交 “fix: 登录异常”,历史就开始撒谎:这个提交不只修了登录。更好的做法是按目的拆开:
git add apps/web/pages/login.vue
git commit -m "fix: handle expired login token"
git add apps/web/components/Header.vue
git commit -m "style: adjust header spacing"提交前看两个 diff:
git diff
git diff --staged
git diff HEADgit diff 看“工作区 vs 暂存区”,也就是还有哪些修改没选进本次提交。git diff --staged 看“暂存区 vs 上一次提交”,也就是这一 commit 实际会带什么。git diff HEAD 则看当前所有未提交变化。
还可以比较两个提交或分支:
git diff a1b2c3d e4f5g6h
git diff main feature/article-searchcommit 是一次可解释的存档
git commit -m "feat: initialize blog project"
git log --oneline一个 commit 记录某一组已暂存的文件变化、作者、时间、说明和父提交。每次提交都有唯一 commit id(通常只需用前几位)。它是本地仓库里的存档点。
git log
git log --oneline
git log --oneline --graph --decorate --all第一条给完整信息;--oneline 压成一行;--graph 用字符画分支;--decorate 显示 main、tag 等名字;--all 显示所有分支。排查历史时最后一条很有用。
提交说明推荐带类型,但别被规范绑住:
feat: 新功能
fix: 修 Bug
docs: 文档
style: 不影响逻辑的格式或样式
refactor: 重构
test: 测试
chore: 杂务、依赖、配置
perf: 性能
ci: 持续集成
build: 构建系统
revert: 撤销某个提交个人项目的标准是半年后自己看得懂;团队项目要遵循团队已有约定。比起漂亮前缀,更重要的是一条提交只描述一件完整的事。
分支:把半成品隔离起来
main 已经部署并稳定,现在要开发文章搜索。直接在 main 上连续改几天并不舒服:临时要修线上 Bug,或者功能做到一半需要发版,都会互相绊住。

创建和切换分支:
git branch
git switch -c feature/article-search
git status -sbgit branch 列出本地分支。git switch -c 同时创建并切换到新分支。旧写法 git checkout -b feature/article-search 仍很常见;switch 把“切分支”与“取回文件”分开,语义更清楚。
常见命名有 feature/article-search、fix/login-token、hotfix/payment-error、refactor/article-service 和 release/v1.2.0。规则可以不同,但名字最好说明用途。
分支不是复制整个项目的一堆文件夹,更像一条指向不同提交的可移动标签。初学阶段记住实际效果即可:你可以在 feature 分支提交搜索功能,不影响 main。
合并 feature 分支
搜索完成后:
git switch main
git pull --ff-only
git merge feature/article-search
git push当前所在分支决定“合进谁”。上面的意思是把 feature/article-search 合进 main,绝不是反过来。git pull --ff-only 会在必须产生合并提交时停下来,给你时间判断,适合主分支更新。
如果 main 自 feature 创建后没新增提交,Git 常能直接快进(fast-forward),只是把 main 标签推到 feature 的末端。如果两边都有提交,Git 可能创建一个 merge commit,用来说明两条历史在这里汇合。都正常,团队习惯不同。
冲突:不是 Git 坏了,是它拒绝瞎猜
main 和 feature/site-title 都修改了 app.vue 的同一行:
// main
"hljs-keyword">const title = "我的博客"
// feature/site-title
"hljs-keyword">const title = "杨帆的 AI 博客"合并时 Git 会停下:
<<<<<<< HEAD
"hljs-keyword">const title = "我的博客"
=======
"hljs-keyword">const title = "杨帆的 AI 博客"
>>>>>>> feature/site-titleHEAD 是当前分支,所以上半段是当前 main 的内容;======= 是分隔线;下半段是要合进来的分支内容。冲突不是要求你随机删一边,而是要求你根据产品需求写出最终代码。

安全处理步骤:
git status # 找到冲突文件
# 手动编辑 apps/web/app.vue,删除所有冲突标记
git diff # 看最终结果
git add apps/web/app.vue
git commit最后的 git commit 完成这次 merge。如果刚开始合并就意识到方向错了:
git merge --abort它会尝试把工作区恢复到 merge 开始前。注意:merge 前本来就有未提交修改时,任何中止操作都不如“先 status、先保存”可靠。
远程仓库、clone、push 和 pull
查看远程:
git remote -v
git remote add origin git@github.com:username/my-blog.git
git push -u origin mainorigin 只是远程仓库常用的别名,不是魔法关键字。-u 是 --set-upstream,为本地 main 记住默认对应 origin/main;以后在这个分支直接 git push 或 git pull 即可。
新建本地项目用 git init。别人已有的远程项目则用:
git clone git@github.com:username/my-blog.git
cd my-blogclone 会下载仓库、提交历史和远程配置,并签出默认分支。
远程 URL 可以是 HTTPS,也可以是 SSH。HTTPS 上手快;SSH 配好密钥后不用反复输凭据:
ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com上传的是 .pub 公钥。id_ed25519 是私钥,永远不要发给别人、不要提交进仓库。
git pull 大体可以理解为 git fetch 加 git merge:fetch 只取回远程的新提交和引用,不改当前工作分支;pull 会尝试把它们整合进来。准备开发前,推荐:
git switch main
git pull --ff-only
git switch -c feature/article-search如果你想先看远程多了什么:
git fetch origin
git log --oneline HEAD..origin/main
git diff main origin/main临时插单:stash
正在 feature/article-search 上写一半,线上登录 500 需要立即修。当前代码还不能提交,又不想带着它切分支:
git status
git stash push -m "wip: article search filters"
git switch main
git pull --ff-only
git switch -c hotfix/login-500stash 会把未提交的工作区和暂存区修改临时收起,让工作区回到干净状态。修完并发布后回来:
git switch feature/article-search
git stash list
git stash poppop 会应用最近一份 stash,成功后删除它;git stash apply 只应用、不删除,更适合先试一试。默认 stash 不包含未追踪文件;需要时:
git stash push -u -m "wip: include "hljs-keyword">new files"stash 是短期停车位,不是长期需求管理。放了几周以后,你很可能忘了它为什么存在。
撤销与恢复:先判断你在哪个状态

这部分最容易误伤代码。先问自己:修改有没有 add?有没有 commit?有没有 push?回答不同,命令完全不同。
文件改了,还没 add
想放弃某个文件的工作区改动:
git restore apps/web/pages/login.vue这会用暂存区中的版本覆盖工作区。它会丢掉该文件未保存到 Git 的修改。执行前必须:
git diff -- apps/web/pages/login.vue如果你只是想恢复其中一小段,别急着 restore 整个文件,手动复制或用编辑器比较更稳。
已经 add,但还没 commit
取消暂存,保留文件修改:
git restore --staged apps/web/pages/login.vue老写法是 git reset HEAD 文件名。这里的 --staged 表示只把暂存区恢复到 HEAD,工作区不动。随后用 git diff 确认。
已经 commit,想改最后一次提交
忘记加一个文件,或提交说明写错:
git add README.md
git commit --amend --no-edit--amend 会替换最近一次 commit;--no-edit 保留原提交信息。改说明则:
git commit --amend -m "docs: clarify local setup"若这次提交已推送并被别人基于它开发,不要随便 amend 后 force push。共享历史最好新增一个正常提交。
reset、revert 怎么选
git reset 会移动当前分支的提交指向。三种常见模式:
git reset --soft HEAD~1 # 取消最近一次 commit,改动仍在暂存区
git reset --mixed HEAD~1 # 默认;改动留在工作区,取消暂存
git reset --hard HEAD~1 # 丢弃该 commit 和工作区改动HEAD~1 是 HEAD 的上一个提交。--soft 常用于“提交拆错了”;--mixed 用于重新选择要 add 的内容;--hard 最危险。
⚠️ 执行 git reset --hard 前,先运行 git status、git log --oneline -5 和 git diff;确认没有需要保留的未提交修改。它会直接覆盖工作区。
已经推送到公共分支的错误提交,通常用 revert:
git revert a1b2c3d
git pushrevert 不改旧历史,而是新建一个“反向修改”的 commit。团队能清楚看到撤回了什么,也不会让别人本地历史断裂。简单规则:私人本地分支可谨慎 reset;共享分支优先 revert。
误删代码怎么救
未提交的删除很难保证恢复,所以 IDE 的 Local History、系统备份也重要。已提交过的文件则很好救:
git log --oneline -- apps/web/pages/login.vue
git restore --source a1b2c3d -- apps/web/pages/login.vue
git diff
git add apps/web/pages/login.vue
git commit -m "fix: restore deleted login page"--source 指定从哪个提交取回文件,不会把整个项目穿越回去。
如果你误做了 reset 或 rebase,先别继续操作:
git reflog
git reset --hard HEAD@{1}reflog 记录过 HEAD 曾经指向哪里,是本地“后悔药”。HEAD@{1} 只是例子,必须先看 reflog 选对那一行。
rebase:整理自己的历史,但别重写公共历史
假设 feature 分支上有 D、E 两次提交,而 main 又有 B、C。你想让 feature 看起来像从最新 main 开始:
git switch feature/article-search
git fetch origin
git rebase origin/mainGit 会把 feature 独有的提交临时拿下,将分支放到最新 main 后面,再重放这些修改。提交 id 会变化,因为父提交变了。
遇到冲突:
git status
# 编辑并解决冲突
git add <冲突文件>
git rebase --continue想放弃这次 rebase:
git rebase --abortmerge 保留真实分叉和合并点;rebase 让个人分支历史更直。我的建议很朴素:自己还没分享出去的 feature 分支,可以 rebase 到最新 main;已经被多人共同使用的分支,不要 rebase,更不要用 force push 改写它。
交互式 rebase 可整理本地提交:
git rebase -i HEAD~3编辑器中 pick 保留,reword 改说明,squash 合并到前一条,drop 删除。只对尚未共享的提交使用。每一步不确定时,退出编辑器并 abort,别硬做完。
cherry-pick:只取一条指定提交
hotfix 分支有一个修复提交,但 release 分支也需要它:
git switch release/v1.2.0
git cherry-pick a1b2c3d它把指定 commit 的修改复制到当前分支,生成一个新 commit。发生冲突时解决、add 后:
git cherry-pick --continue放弃则 git cherry-pick --abort。不要把 cherry-pick 当日常合并替代品;它适合挑一个明确的、独立的修复。
发版:tag、release 与 hotfix
当 main 达到 1.2.0:
git switch main
git pull --ff-only
git tag -a v1.2.0 -m "release: v1.2.0"
git push origin v1.2.0-a 创建附注 tag,-m 写说明。tag 固定指向某一个提交,适合标记可部署版本。GitHub 的 Release 往往基于 tag 创建,但两者不是同一件事。
线上发现严重 Bug 时,流程可以是:
git switch main
git pull --ff-only
git switch -c hotfix/login-500
# 修复、测试、提交
git switch main
git merge --no-ff hotfix/login-500
git tag -a v1.2.1 -m "release: fix login 500"
git push origin main --tags--no-ff 强制留下一个 merge commit,有些团队喜欢用它标记功能/修复的边界;不是必需。部署前要跑项目真实的测试、构建和健康检查,tag 不能替代验证。
完成后删除本地和远程分支:
git branch -d hotfix/login-500
git push origin --delete hotfix/login-500-d 会在 Git 判断分支尚未合并时拒绝删除,-D 才是强制删除。优先 -d。
从历史里找答案
查看某次提交细节:
git show a1b2c3d
git show --stat a1b2c3d追查某一行是谁改的:
git blame -L 20,35 apps/web/app.vueblame 显示每行最后一次变更所属的提交、作者和时间。它用于理解上下文,不是追责工具。
“这个 Bug 是哪次提交引入的?”可以用二分查找:
git bisect start
git bisect bad # 当前版本有 Bug
git bisect good v1.1.0 # 已知正常版本
# Git 切到中间提交;运行测试后标记 good 或 bad
git bisect good
git bisect bad
git bisect resetbisect 每次把范围砍半,适合提交很多且问题可稳定复现的情况。最后的 reset 很重要,它让你回到开始 bisect 前的分支。
只想看看旧版本,不想修改 main:
git switch --detach a1b2c3d
# 查看、运行或比较
git switch main这会进入 detached HEAD:你停在某个提交上,而不是某个分支。临时查看没问题;如果在这里做了有价值的提交,立刻创建分支保存它:
git switch -c investigate/login-regressionHEAD、main 与 origin/main
HEAD 表示“你现在所在位置”,通常指向当前分支最新提交。main 是本地分支标签。origin/main 是 Git 上次从 origin 看到的远程 main 状态,它不是实时联网查询结果。
HEAD -> main -> 本地最新提交
origin/main -> 上次 fetch/pull 后看到的远程 main这也是为什么有时先 git fetch 再比较,判断会更可靠。
Pull Request 与团队协作
个人项目也可以用 PR:把 feature 分支 push 上去,创建 Pull Request,先看 diff、跑 CI,再合并。多人项目里它更像一张变更说明和审查记录。
一个实用日常流程:
git switch main
git pull --ff-only
git switch -c feature/article-search
# 编码期间反复:
git status -sb
git diff
git add <明确的文件>
git diff --staged
git commit -m "feat: add article keyword search"
git push -u origin feature/article-search
# 创建 PR、检查 CI、处理 review团队里这些习惯很值钱:
不要直接在 main 上开发,除非团队明确允许。
开始新功能前更新 main;长生命周期分支要定期同步。
一个提交不要塞十件无关的事。
永远不要提交 .env、私钥、生产导出数据。
不要 force push main,也不要对公共分支 rebase。
冲突标记不是“删掉就好”,要理解两边意图并跑测试。
大改动前先做一个可运行的提交;难以拆分时至少临时 stash。
最常见的问题,按现象处理
git push 被拒绝
通常是远程比你领先。不要立即 force push:
git fetch origin
git log --oneline HEAD..origin/main
git pull --rebase # 仅当你在个人 feature 分支且团队允许
# 或 git pull,解决合并冲突后再 push公共 main 通常需要先同步,再用正常 merge/revert 处理。
切分支时提示本地修改会被覆盖
这说明 Git 在保护你的工作区。三种选择:提交一个 WIP commit、git stash,或先把改动收尾。不要为了切过去而上来就 reset --hard。
add 错了文件,或者 commit 写错分支
尚未提交:git restore --staged 文件名。已经提交但没 push:根据需要 reset --soft HEAD~1,然后在正确分支重新提交。已经 push 且是共享分支:优先 revert,再新建正确提交;若涉及敏感信息,立即废弃泄露的凭据,并通知团队处理历史清理。
pull、merge、rebase 出现冲突
都遵循同一原则:git status 找文件,理解两边改动,编辑最终内容,跑测试,再 add。对应的结束命令分别是 git commit、git rebase --continue、git cherry-pick --continue。方向错了才用各自的 --abort。
.gitignore 不生效
检查文件是否早已被追踪:
git ls-files .env
git rm --cached .env还可用 git check-ignore -v 文件名 找到究竟哪条规则生效。
命令安全等级与执行前检查
等级 | 常用命令 | 建议 |
|---|---|---|
🟢 比较安全 | status、log、diff、show、branch、fetch | 可大胆用于观察 |
🟡 会改变状态但通常易恢复 | add、commit、merge、stash、restore --staged、revert | 先看 status/diff |
🔴 需明确确认 | reset --hard、clean -fd、push --force、rebase 已共享分支 | 先确认目标、备份或创建分支 |
git clean 用来删除未追踪文件:
git clean -n # 只预览,务必先运行
git clean -fd # 删除未追踪文件和目录它很适合清理构建垃圾,但 -fd 会删除真实的新文件。先 -n,没有例外。
force push 有存在理由,例如你刚 rebase 了自己的个人 PR 分支:
git push --force-with-lease origin feature/article-search优先 --force-with-lease。它会在远程分支出现你没见过的新提交时拒绝覆盖;--force 没有这层保护。即便如此,也先确认该分支只有你自己在用。
让 Git 少占脑容量
可以设置几个只读 alias:
git config --global alias.st "status -sb"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.last "log -1 --stat"以后输入 git st、git lg。alias 只是快捷方式,和团队项目无关;不要复制来历不明、包含 destructive 操作的 alias。
Hooks 是 Git 在 commit、push 等节点触发的脚本。常见用途是 lint、测试、格式化或阻止提交密钥。它能减少低级失误,但不能取代 CI:开发者可以跳过本地 hook,服务器端仍应检查。
大二进制文件不适合普通 Git 历史。视频、模型、设计源文件频繁改动时,考虑 Git LFS、对象存储或专门的资产管理方式。Git 也不是完整备份系统:远程仓库、数据库备份、生产发布物和密钥管理,各自仍需独立方案。
收尾:开发前后各看一眼
开始前:
git status -sb
git switch main
git pull --ff-only提交前:
git status
git diff
git diff --staged推送前:
git log --oneline -5
git status -sb出了问题时:
git status
git diff
git log --oneline --graph --decorate --all
git reflogGit 熟练的标志,不是背得出多少命令,而是代码出问题时,你知道它现在在工作区、暂存区、提交历史还是远程分支的哪一层;知道先看什么,也知道怎样安全地退回来。
1 本文章标题: Git 从入门到真正会用:一篇写给开发者的实战指南
2本文章地址:https://yf2029.com/articles/git-从入门到真正会用-一篇写给开发者的实战指南
3 本站文章内容仅供学习、交流与界面验证参考,如有版权或引用问题,请联系站点维护者处理。
4 本站不对文中外部链接、第三方资源或读者据此操作产生的结果承担保证责任。


评论
登录后即可发表评论和回复。