周三技术交流会 · 技术分享

Git 项目管理
从一个人写代码,到一群人写代码

不背术语、不啃文档。用生活里的比喻把 Git 讲明白,再把单人开发和团队协作两个场景,从头到尾完整走一遍。

⏱ 约 30 分钟 🎯 面向新人 / 实习生 🏷 标「核心」的必看,标「进阶」的课后翻

00开场:为什么非学 Git 不可核心

先讲清楚"不用 Git 会有多惨",后面每一个命令你才会觉得是刚需,而不是负担。

先看一个大家都经历过的场景 —— 交毕业论文的时候,你的文件夹是不是长这样:

📁 我的论文/
   论文.docx
   论文_修改版.docx
   论文_修改版2.docx
   论文_最终版.docx
   论文_最终版_真的最终版.docx
   论文_最终版_听导师的改完了.docx
   论文_最终版_这次真的不改了.docx      ← 现在告诉我,哪个是最新的?
   论文_备份_20260731.docx           ← 这个跟上面那个差在哪?

你能想起来"这次真的不改了"和"听导师的改完了"之间,到底改了哪几段吗?想不起来。而写代码的时候,这个问题会放大一百倍:

  • 改着改着功能突然崩了,想退回到昨天那个能跑的版本 —— 退不回去了
  • 想试一个大胆的重构,又怕搞砸现在能跑的代码 —— 不敢动手
  • 三个人同时改一个项目,靠微信互传压缩包 —— 互相覆盖,代码凭空消失
  • 线上出 bug,想知道这行代码是谁、什么时候、为什么加的 —— 无从查起
🎮 一句话比喻

Git 就是给你的代码装了一套"游戏存档系统"。

打 Boss 之前先存个档(commit),死了随时读档(reset);想试试另一条剧情线,开个新档位(branch),玩坏了删掉就行,完全不影响主线存档。而且这套存档还是全队共享的,队友的进度能同步给你。

所以 Git 解决的其实就是四件事。记住这四个词,后面所有命令都能挂在上面:

留痕
每次改动
都记下来
后悔
随时退回
任意历史点
分身
并行开发
互不干扰
合流
多人成果
安全合并

01原理篇:Git 到底是个啥

这一章不背命令,只建立"心智模型"。模型对了,命令是自然推导出来的;模型错了,你会一辈子靠死记硬背过日子,一出问题就抓瞎。

1.1 版本控制到底解决什么问题核心

版本控制系统(VCS)干的事,说白了就是:帮你自动管理刚才那个"论文_最终版"文件夹,而且管得比你好一万倍。

它记录的不是一堆文件副本,而是一条时间线。时间线上每个点都标注清楚了:谁、什么时候、改了哪些文件的哪几行、以及为什么改(提交说明)。

1.2 集中式 vs 分布式:Git 凭什么赢了解即可

老一辈的 SVN 是集中式的,Git 是分布式的。用图书馆打个比方:

SVN = 全世界只有一座中央图书馆

所有历史资料只存在总馆。你要查一本旧书、要借书还书,必须联网跑一趟总馆

总馆着火了(服务器挂了)→ 全公司历史记录全没
断网了 → 你什么都干不了。

Git = 每个人家里都有一座全量分馆

你 clone 一次,就把整座图书馆连同全部历史搬回了自己电脑。查历史、开分支、提交存档,全程不用联网,快得飞起。

中央服务器炸了?随便找个同事的电脑推一份上去就恢复了。

💡 这解释了新人的第一大困惑

"我明明 commit 了,为什么同事看不到我的代码?"—— 因为 commit 只是存进了你家里那座分馆。要让别人看到,必须再 git push 送到总馆去。
commit ≠ 上传。 这句话请写在小本本上。

1.3 三个区域:整个 Git 最重要的一张图核心

如果今天你只记住一张图,请记住这张。90% 的 Git 命令,本质都是在这几个区域之间搬东西。

工作区
Working Directory
你正在编辑的文件
git add
暂存区
Staging / Index
准备打包的东西
git commit
本地仓库
Local Repo
你电脑里的历史
git push
远程仓库
Remote
GitHub / 公司 GitLab
📦 比喻:搬家寄快递

工作区=你的房间。东西摊得到处都是,你正在收拾。

暂存区=房间中间那个敞着口的纸箱。你挑好了要寄走的东西,一件件放进箱子(git add)。放错了随时能拿出来。

本地仓库=箱子封上胶带、贴好标签,堆进自家仓库(git commit,标签就是提交说明)。这一步之后这箱东西就永久存档了。

远程仓库=把箱子交给快递发出去git push),同事才能收到。

为什么要多一个"暂存区"这么麻烦?因为它让你能挑着提交。比如你这一下午干了两件不相干的事:修了一个 bug,又顺手改了 README。你可以分两次装箱、分两次贴标签:

# 第一箱:只装 bug 修复相关的文件
git add src/login.js
git commit -m "fix: 修复登录按钮点击两次才生效的问题"

# 第二箱:只装文档
git add README.md
git commit -m "docs: 补充本地启动步骤"

这样历史记录清清楚楚,将来哪一箱有问题就退哪一箱。要是一股脑全塞进一个箱子,将来想单独退回其中一件东西,就只能拆箱重装了。

1.4 一次 commit 到底存了什么核心

新人常以为 commit 存的是"我改了哪几行"。其实不是。Git 存的是"整个项目当时长什么样"的一张完整快照。

📸 比喻:拍照片,不是记流水账

每次 commit,Git 就把你整个项目咔嚓拍一张全景照片存起来。100 次提交就是 100 张照片,连起来是一部定格动画,你可以随时跳到任意一帧。

担心占空间?Git 很鸡贼:没改动的文件,新照片里直接引用旧照片里的那一份,不重复存。所以 100 张照片其实没多大。

每张"照片"上都贴着一串身份证号,就是那个 40 位的哈希值,比如 a3f5c9e...。平时用前 7 位就够了(a3f5c9e)。它是这次提交的唯一 ID,全世界不重复。

git log --oneline
a3f5c9e (HEAD -> main) feat: 新增用户头像上传
7b2e841 fix: 修复登录按钮重复提交
c1d0f33 docs: 补充启动步骤
e5a7b90 chore: 初始化项目

同时,每张照片还记着"我的上一张是谁"。所以这些提交串成了一条链子 —— 这就是所谓的"提交历史"。

1.5 分支只是一张便利贴核心

很多人一听"分支"就头大,觉得是很重的东西。真相是:一个分支只是一个 41 字节的小文件,里面就写了一个提交 ID。

🔖 比喻:书签 / 便利贴

刚才那串提交是一本书的书页。分支就是夹在某一页上的一张便利贴,上面写着分支名。

新建分支 = 再撕一张便利贴贴在同一页上(零成本,一瞬间的事,绝不是复制整本书!)。
在这个分支上提交 = 书多写了一页,便利贴自动往后挪一页。
删除分支 = 撕掉便利贴,书页本身还在。

HEAD 是什么?HEAD 就是"你现在正在读哪张便利贴",也就是"你人在哪儿"。git switch dev 就是把你的视线挪到 dev 那张便利贴上。

e5a7b90 c1d0f33 7b2e841 a3f5c9e 9d4c1a2 main feature/avatar ↑ HEAD 现在在这
下面那条链是 main(主线),从 7b2e841 岔出去的是新功能分支。两条线各写各的,互不影响。

1.6 进阶:Git 底层就四种"零件"进阶 · 可跳过

这段属于"知道了会更踏实",30 分钟的分享一带而过就行。Git 的 .git 目录里其实只存了四种对象:

对象存的是大白话
blob文件内容一个文件的内容本身(不含文件名)。内容一样的两个文件,Git 只存一份。
tree目录结构一个文件夹的目录表:记录"这个目录下有哪些文件名,各自指向哪个 blob"。
commit一次提交指向一个顶层 tree(=那张快照),外加作者、时间、说明、以及父提交是谁。
tag标签对象给某个 commit 起的一个好记的名字,比如 v1.2.0

所以整个 Git 就是一个"用哈希值当 key 的小型数据库" + 一堆指来指去的指针。你可以亲手扒开看看:

git cat-file -p HEAD          # 看看最新那次提交里到底存了啥
tree 9a3b1c...                # ← 指向那张"快照"
parent 7b2e84...              # ← 指向上一次提交
author linxingyu <xxx@example.com> 1754...

feat: 新增用户头像上传

02场景一:一个人开发

目标:把 Git 当成你的"个人存档 + 后悔药"。这一章的命令,是每天要敲几十遍的肌肉记忆。

2.1 第一次用:三行配置核心

装完 Git 先做这个,否则你的每次提交都会显示"提交人:不知道是谁"。

# 告诉 Git 你是谁(会写进每一次提交记录,同事就靠这个找你)
git config --global user.name "linxingyu"
git config --global user.email "linxingyu@example.com"

# 让中文文件名不显示成一串数字乱码
git config --global core.quotepath false

# 检查一下配好没
git config --global --list
⚠️ 实习生高频翻车点

邮箱要用公司代码平台账号绑定的那个邮箱。用错了,平台认不出是你提交的,你的贡献记录是灰的,Code Review 也 @ 不到你。

怎么开局:两种情况

# 情况 A:从零开始一个新项目
mkdir my-blog && cd my-blog
git init                        # 在当前文件夹建一个空仓库(会多出一个 .git 隐藏文件夹)

# 情况 B:接手已有项目(实习生 99% 是这种)
git clone https://github.com/company/project.git
cd project                    # clone 会自动建好文件夹并把全部历史拉下来
🔍 .git 文件夹是什么

它就是那座"分馆"本身 —— 所有历史、所有分支都在里面。把它删了,项目就退化成一堆普通文件,历史全没。所以:永远不要手动去动 .git 里的东西。

2.2 日常四连招:status → add → commit → log核心

这四个命令占你日常 Git 使用的 80%。

① 我现在改了啥?(最该养成的习惯:一有疑问就敲它)
git status
On branch main
Changes not staged for commit:   ← 红色:改了但还没放进箱子
        modified:   src/login.js
Untracked files:                ← 红色:Git 压根没见过的新文件
        src/avatar.js

② 具体改了哪几行?
git diff                    # 看工作区的改动(还没 add 的)
git diff --staged          # 看已经放进箱子的改动

③ 装箱
git add src/login.js       # 装一个文件
git add src/               # 装一个文件夹
git add .                  # 装当前目录下所有改动(最常用,但记得先 status 确认别装错)

④ 封箱贴标签
git commit -m "feat: 新增用户头像上传功能"

⑤ 回顾历史
git log --oneline --graph --all    # 一行一条 + 画出分支图,强烈推荐
💡 一个提效小技巧

git commit -am "xxx" 可以把 add 和 commit 一步做完。但注意:它只对 Git 已经跟踪过的文件生效,新建的文件不会被带上。所以新文件还是得老老实实先 git add

2.3 .gitignore:别把垃圾提上去核心

项目里有很多东西不该进仓库:依赖包(几万个文件)、编译产物、IDE 配置,还有最要命的 —— 密码和密钥。

🚪 比喻:门上挂"请勿打扫"

.gitignore 就是贴在门上的一张单子,写着"这些房间你别进、别管"。Git 看到单子上的文件就直接当没看见。

# .gitignore 文件放在项目根目录,直接写文件名 / 路径 / 通配符

# 依赖包 —— 别人 npm install 一下就有,没必要提交几万个文件
node_modules/
target/
venv/

# 编译产物
dist/
build/
*.class
*.pyc

# IDE 和系统垃圾文件
.idea/
.vscode/
.DS_Store

# ⚠️ 最重要:任何带密码、密钥、token 的文件
.env
*.key
config/secret.yml
🔥 血泪教训:密码不小心提交上去了怎么办

删掉文件再提交一次 没有用 —— 历史记录里还躺着。正确做法是:
① 立刻把那个密码 / 密钥作废重置(这一步最重要,必须马上做);
② 再去清理历史(用 git filter-repo 或 BFG 工具,之后要通知全组重新 clone)。
顺序不能反:清历史很慢,而爬虫扫公开仓库里的密钥只要几十秒。

已经提交过的文件,怎么让 Git 不再管它

# 加进 .gitignore 之后,还得手动把它从 Git 的跟踪列表里踢出去(本地文件会保留)
git rm --cached .env
git rm -r --cached node_modules/
git commit -m "chore: 停止跟踪本地配置文件"

2.4 后悔药大全:改错了怎么办核心 · 全场最实用

新人最怕的就是"我好像把东西弄没了"。其实只要你提交过,Git 里基本没有真正丢得掉的东西。关键是搞清楚:你后悔的那个东西,现在在哪个区域。

你的处境命令大白话
文件改乱了,还没 add,想变回上次提交的样子 git restore 文件名
老写法:git checkout -- 文件名
把房间里那件东西擦干净复原
⚠️ 改动直接消失、救不回来,想清楚再敲。
已经 add 了,想从箱子里拿出来(改动保留) git restore --staged 文件名
老写法:git reset HEAD 文件名
把东西从纸箱里掏回房间,东西本身不动。很安全。
刚 commit 完,发现提交说明写错了 git commit --amend -m "新说明" 撕掉标签重写一张。也可以顺手把漏掉的文件 add 了再 amend,合并进上一次提交。
撤销最近一次提交,改动留着继续改 git reset --soft HEAD~1 拆箱,但东西还在箱口(回到 add 之后的状态)。最温柔的一档。
撤销提交,改动退回工作区 git reset HEAD~1
(默认就是 --mixed)
拆箱,东西倒回房间。最常用的一档。
撤销提交,连改动一起彻底抹掉 git reset --hard HEAD~1 连箱带货一起烧掉
⚠️ 核武器,敲之前先深呼吸。
要撤销的提交已经 push 到公共分支了 git revert 提交ID 不删历史,而是新加一次"反向操作"的提交。别人拉下来不会乱。这是团队场景唯一正确的撤销方式。
reset --hard 完就后悔了 😱 git reflog 找到 ID,
git reset --hard 那个ID
Git 的黑匣子。它记录了你 HEAD 走过的每一步,哪怕提交已经"消失",这里还查得到。终极后悔药。
🎚 比喻:reset 三档,就像删微信消息

--soft=消息撤回了,但内容还在输入框里,随时能再发。
--mixed=内容退回草稿箱,得重新粘一遍才能发。
--hard连草稿一起清空,什么都没了。

# reflog 实战:手滑把最新提交 reset --hard 掉了
git reflog
7b2e841 HEAD@{0}: reset: moving to HEAD~1
a3f5c9e HEAD@{1}: commit: feat: 新增用户头像上传   ← 就是它!还活着!
7b2e841 HEAD@{2}: commit: fix: 修复登录按钮

git reset --hard a3f5c9e       # 满血复活 🎉
💡 送实习生一句保命口诀

"任何危险操作之前,先 git commit 一次,或者 git stash 一下。"
只要东西进过 Git 的仓库,reflog 就能把它捞回来。没进过仓库的改动,神仙也救不了。

2.5 分支:你的安全实验田核心

单人开发也要用分支!典型玩法:主线 main 永远保持随时能跑,任何新功能、任何大改,都开一个分支去折腾。

git branch                        # 看有哪些分支,带 * 的是当前所在
git switch -c feature/avatar     # 新建并切过去(新写法,推荐)
# 等价老写法:git checkout -b feature/avatar

# ... 在这个分支上放心大胆地改、提交 ...

git switch main                  # 切回主线
git merge feature/avatar         # 把实验成果合并回主线
git branch -d feature/avatar     # 合并完了,撕掉这张便利贴
git branch -D feature/avatar     # 大写 D = 强删(没合并过也删,那些提交就找不着了)
🌿 分支命名的行业惯例

feature/user-avatar 新功能 · fix/login-double-submit 修 bug
hotfix/pay-timeout 线上紧急修复 · refactor/order-module 重构
格式就是 类型/简短描述,用英文小写加连字符。
千万别叫 testmybranchxxxnew111 —— 一周后你自己都不知道那是啥。

2.6 stash:临时插单神器核心

经典场景:你正在开发新功能,代码改了一半(这状态根本没法提交),产品经理突然冲过来:"线上炸了!你先去修个紧急 bug!"

🗄 比喻:把桌上的活儿"整个端进抽屉"

git stash = 把你桌上摊了一半的活儿整个端起来塞进抽屉,桌面立刻变干净(回到上次提交的状态),你就能腾出手去干急活。忙完了再从抽屉里端回来,摊在桌上接着干。

git stash push -m "头像上传写了一半"   # 塞进抽屉,工作区立刻变干净
git switch -c hotfix/pay-timeout        # 去修紧急 bug
# ... 修完、提交、合并、上线 ...

git switch feature/avatar              # 回到原来的分支
git stash list                          # 看看抽屉里有啥
stash@{0}: On feature/avatar: 头像上传写了一半
git stash pop                           # 端回桌面,并从抽屉移除(最常用)
# git stash apply  = 端回来,但抽屉里还留一份备份

2.7 tag:给版本盖个章进阶

发版的时候用。tag 是钉死在某个提交上的名牌,跟分支不一样:分支会往前跑,tag 永远不动。

git tag -a v1.2.0 -m "2026 年 8 月版本,新增头像上传"
git tag                       # 列出所有 tag
git push origin v1.2.0       # ⚠️ tag 不会跟着 git push 自动上传,得单独推
git push origin --tags       # 或者一次全推上去

2.8 完整实战演练:从 0 做一个个人博客核心 · 建议现场演示

把上面所有东西串成一条真实的工作流。建议边讲边在终端敲一遍。

  1. 开工
    mkdir my-blog && cd my-blog && git init
    echo "node_modules/" > .gitignore
    git add . && git commit -m "chore: 初始化项目"
  2. 写首页,提交一版能跑的
    # 写完 index.html 之后
    git status                     # 习惯性确认一下
    git add index.html
    git commit -m "feat: 完成博客首页布局"
  3. 想加评论功能,但不确定能不能搞定 → 开分支
    git switch -c feature/comments
    # 折腾两小时,提交了 3 次……
  4. 突然发现首页有个错别字,得紧急改 → stash
    git stash push -m "评论功能写一半"
    git switch main
    # 改掉错别字
    git commit -am "fix: 修正首页标题错别字"
    git switch feature/comments && git stash pop   # 回来继续干
  5. 评论功能搞定了 → 合并回主线
    git switch main
    git merge feature/comments
    git branch -d feature/comments
    git log --oneline --graph --all     # 欣赏一下自己干净的历史
  6. 发布 → 打标签
    git tag -a v1.0.0 -m "博客第一个正式版本"

03场景二:一群人开发

单人开发是"自己跟自己玩",多人协作才是 Git 真正的主场,也是新人最容易出事的地方。这一章的核心就一句话:怎么在不踩到别人脚的前提下,把自己的代码安全地并进去。

3.1 远程仓库与 origin核心

回到图书馆那个比喻:每个人家里都有分馆,那大家怎么同步?—— 约定一个"总馆",所有人都跟总馆对齐。这个总馆就是远程仓库(GitHub / Gitee / 公司 GitLab)。

origin 不是什么关键字,它只是总馆地址的默认昵称。你 clone 的时候 Git 自动给它起的名,就像你把老板的手机号存成"老板"一样。

git remote -v                    # 看看我连着哪些"总馆"
origin  https://github.com/company/project.git (fetch)
origin  https://github.com/company/project.git (push)

git remote add origin 仓库地址    # 本地 init 的项目,手动关联远程
🌐 三个"分支"名,别搞混

main     → 你本地的分支(你自己的便利贴)
origin/main  → 你上次联网时看到的远程分支状态(一份"缓存的快照")
远程真正的 main → 服务器上此刻的状态,可能同事刚推了新东西,你还不知道

所以 origin/main 不会自己更新,必须 git fetch 才会刷新。

3.2 fetch / pull / push:三个动作说清楚核心

远程仓库
origin
git fetch
只下载不合并
本地仓库
origin/main 更新了
git merge
你的工作区
代码真的变了
📬 比喻:快递驿站

git fetch去驿站把包裹取回家,但先不拆。你可以先看看里面是啥(git log origin/main),确认没问题再拆。绝对安全,不会动你的代码。

git pull取回来 + 当场拆开 + 直接摆进你房间。等于 fetch + merge 两步并一步。方便,但可能当场跟你桌上的东西撞车(冲突)。

git push把你的包裹寄出去给大家。

# 每天上班第一件事:拉最新代码
git pull                             # 最常用
git pull --rebase                    # 进阶:历史更干净,见 3.7

# 写完了推上去
git push
git push -u origin feature/avatar   # 第一次推新分支要带 -u,建立"绑定关系"
# 之后这个分支就可以直接 git push / git pull 了
⚠️ 新人 90% 的协作事故,都源于"不 pull 就开干"

正确习惯:每天开工前、每次开新分支前、每次准备 push 前,都先 pull 一次。
你基于三天前的代码写了一整天,同事那三天早把地基改了 —— 这时候合并起来才叫惨。

3.3 分支策略:团队怎么定规矩核心

分支策略就是团队的"交通规则"。业界常见三种,对我们这种规模的团队,我建议用中间那种

策略长什么样适合谁 / 大白话
Git Flow
重型
main / develop / feature/* / release/* / hotfix/* 五种分支 规矩最全,但也最繁琐。适合有明确版本号、按季度发版的产品(比如客户端 App、私有化部署的系统)。小团队用会累死。
GitHub Flow
推荐 👍
只有一条 main,所有人从 main 开 feature/* 分支,做完提 PR 合回 main 简单到实习生半天就能学会。main 永远保持可发布,随时能上线。绝大多数 Web 项目、内部系统都够用。
主干开发
Trunk Based
基本只有 main,分支活不过一天,靠功能开关控制上线 要求团队自动化测试非常强、CI/CD 完善。新人多的团队不建议,容易把主干搞崩。
main feature/avatar(小王) feature/comments(小李) PR 合并 PR 合并
GitHub Flow:每人从 main 拉自己的分支干活,做完各自提 PR 合回 main。互不打扰,合并有人把关。
💡 给实习生的两条铁律

① 永远不要直接在 main 上写代码、直接 push。公司一般也会把 main 设成"受保护分支",你想推也推不上去。
② 分支活得越短越好。一个分支最好 1~3 天就合回去。拖两周的分支,合并时冲突能让你怀疑人生。

3.4 PR 全流程实操(GitHub / Gitee)核心 · 实习生每天都要走

PR = Pull Request,中文叫"合并请求"。说白了就是:「我写完了,请你们看一眼,觉得没问题就帮我合进主线。」

📝 比喻:交作业 / 报销流程

你直接往 main 推代码,等于绕过财务直接从公司账上拿钱
而 PR 流程是:填单子(写 PR 描述)→ 交给主管审批(Code Review)→ 主管签字(Approve)→ 财务打款(Merge)。
麻烦一点,但出了问题有记录、有人一起把关,谁也不用背锅。

  1. 同步主线,从最新的 main 开分支
    git switch main
    git pull                                # 一定要先拉最新!
    git switch -c feature/user-avatar
  2. 写代码,小步多次提交
    git add .
    git commit -m "feat: 新增头像上传接口"
    git commit -m "feat: 前端头像裁剪组件"
    git commit -m "test: 补充头像上传单测"
  3. 推到远程
    git push -u origin feature/user-avatar

    推完终端会直接给你一个 PR 链接,点进去就行。

  4. 在网页上创建 PR,把描述写清楚
    ## 这个 PR 做了什么
    支持用户上传自定义头像,含裁剪、压缩、上传三步。
    
    ## 为什么要做
    关联需求 #128,目前用户只能用系统默认头像。
    
    ## 怎么测
    1. 进入「个人中心 → 编辑资料」
    2. 点击头像,选一张 >2MB 的图,确认能正常裁剪并上传成功
    3. 选一个 .txt 文件,确认有格式错误提示
    
    ## 截图
    (贴上效果图 / 录屏)
    
    ## 风险点
    改动了公共的文件上传工具类,麻烦重点看一下 utils/upload.js
  5. 等 Review,根据意见改
    # 直接在同一个分支继续提交、继续 push,PR 会自动更新,不用重开
    git commit -am "fix: 按 review 意见补充文件大小校验"
    git push
  6. 审批通过 → 合并 → 删分支
    # 网页上点 Merge 即可,一般会顺手勾选"删除源分支"
    git switch main
    git pull                                 # 把刚合并的成果拉回本地
    git branch -d feature/user-avatar      # 本地也清理掉

三种合并方式,选哪个?进阶

方式效果什么时候用
Merge commit保留你分支上所有提交,另外多一个"合并提交"想完整保留开发过程时
Squash and merge
最推荐
把你分支上那十几次"改改改""再改改"压成干干净净的一条提交合进 main日常首选,main 的历史特别清爽,一个功能就一条记录
Rebase and merge把你的提交一条条搬到 main 顶上,历史是一条直线希望保留每条提交、又不想有分叉时
🍴 Fork 是什么?什么时候需要

Fork = 把别人的仓库整个复制一份到你自己账号下。因为你对人家的仓库没有写权限,只能在自己的复制品上改,然后跨仓库提 PR。

给开源项目贡献代码用 Fork 流程;公司内部项目你本来就有权限,直接开分支即可,不用 Fork。

3.5 Code Review 怎么做才不伤感情核心

✅ 提 PR 的人

  • PR 要小。300 行以内最好。一个 2000 行的 PR,没人真的看得动,最后只会被草草 Approve。
  • 描述写清楚"做了什么 / 为什么 / 怎么测",别让 reviewer 猜。
  • 自己先 Review 一遍:调试用的 console.log、注释掉的死代码、密钥,全都清掉。
  • 被提意见 ≠ 被否定,是别人在帮你免费兜底

💬 评审的人

  • 对事不对人。说"这里空指针风险",别说"你怎么这都不会"。
  • 区分严重程度:[必须改] / [建议] / [闲聊],让对方知道哪些是硬伤。
  • 提问式表达:"这里如果 list 为空会怎样?" 比 "这里写错了" 好得多。
  • 写得好的地方也说一句 👍,Review 不是只挑刺。

3.6 冲突:其实一点都不可怕核心 · 建议现场演示

先破除恐惧:冲突不是报错,不是你把仓库搞坏了。它只是 Git 在说:"这一行你俩都改了,我不敢替你们做主,你自己决定留哪个。"

📄 比喻:两个人改同一份合同

你把第 5 条改成"付款期 30 天",同事把第 5 条改成"付款期 45 天"。
秘书(Git)合并的时候傻了:别的条款你俩改的地方不一样,我自己就合好了;但第 5 条撞车了,我不敢瞎猜,只能把两个版本都抄给你,你来定。

冲突长什么样

git pull
CONFLICT (content): Merge conflict in src/config.js
Automatic merge failed; fix conflicts and then commit the result.

# 打开 src/config.js,你会看到 Git 插进来的这几行标记:
<<<<<<< HEAD
  timeout: 3000,          // ← 这是你写的(当前分支)
=======
  timeout: 5000,          // ← 这是别人写的(要合进来的分支)
>>>>>>> origin/main

解决三步走

  1. 决定留什么,然后把标记行全删掉

    留你的、留他的、或者两个都要、甚至重写一个新的都行。关键是最后文件里不能再有 <<< === >>> 这些标记。

    // 比如商量后决定用 5000
      timeout: 5000,
  2. 告诉 Git "这个文件我处理好了"
    git add src/config.js
    git status                  # 确认所有冲突文件都处理完了
  3. 提交,完事
    git commit                  # 直接回车用默认的合并说明就行
    # 中途反悔想放弃这次合并:git merge --abort  (一键回到合并前)
💡 三个降低冲突痛苦的实用建议

① 不确定怎么改就去问人。冲突的另一半代码是同事写的,跑去问他两句,比你自己猜半小时强一百倍。删掉别人的代码是很严重的事。

② 用 IDE 解决,别用记事本。VS Code / IDEA 都有可视化的冲突界面,直接点"采用当前 / 采用传入 / 都保留",比手删标记安全得多。

③ 勤 pull、分支别拖太久。冲突的严重程度跟"你多久没同步主线"成正比。

3.7 merge vs rebase:一场经典之争进阶 · 但值得讲

🛣 比喻:两条路怎么汇合

merge = 修一个立交桥路口。两条路真的在这里汇合了,历史上永远留着这个岔路口。真实、完整,但历史图会像地铁线路图一样花。

rebase = 把你那条路整个拆下来,重新接到主路的最末端。看起来就像你一直是在主路最新的位置上修路的。历史是一条漂亮的直线,但那是"整理过"的历史,不是当时真实发生的。

git merge

      A---B---C  feature
     /         \
D---E---F---G---H  main
              合并提交 ↑

✅ 安全、不改写历史、真实记录
❌ 分支多了以后 log 图很乱

git rebase

D---E---F---G---A'--B'--C'  main
                (原来的 A B C
                  被复制成新提交)

✅ 历史一条直线,非常好读
❌ 改写了提交 ID,用错了会坑队友

# 典型用法:把主线的最新代码"垫"到我的分支下面
git switch feature/avatar
git rebase main
# 有冲突就解决,然后:
git add 冲突文件 && git rebase --continue
# 想放弃:git rebase --abort

# 日常拉代码时用 rebase 代替 merge,避免一堆没意义的"合并提交"
git pull --rebase
# 嫌每次都要加参数,可以设成默认:
git config --global pull.rebase true
🚨 rebase 黄金法则(务必记住)

永远不要对「已经推送到远程、并且别人可能已经拉过」的分支做 rebase。

为什么?rebase 会把提交复制成全新的提交(ID 全变了),相当于你把大家共同踩着的地板整个换了一遍。同事再 pull 的时候,Git 会以为凭空多出一堆提交,历史彻底乱套。

安全口诀:rebase 只用在「自己的、还没分享出去的」分支上。公共分支老老实实用 merge。

3.8 提交信息规范:写给三个月后的自己核心

❌ 反面教材

update
修改
1
fix bug
提交一下
aaa
终于好了
测试

三个月后线上出事,你对着这样一屏 log 定位问题,只能一条条点开看 diff。

✅ 正确姿势(Conventional Commits)

feat: 新增用户头像上传
fix: 修复订单金额精度丢失
docs: 补充接口文档
style: 格式化代码,不改逻辑
refactor: 重构支付回调逻辑
perf: 优化列表查询,减少 N+1
test: 补充下单流程单测
chore: 升级 axios 到 1.7.2

格式很简单:类型: 做了什么。复杂的改动可以加正文和关联单号:

git commit -m "fix: 修复用户头像上传超过 2MB 时失败" \
           -m "原因:Nginx 默认 client_max_body_size 为 1M。
已调整为 10M,并在前端加上传前大小校验。

Closes #128"
💡 一句话检验标准

"三个月后的你,光看这条提交信息,能不能想起来当时改了啥、为什么改?"能,就是好的提交信息。

3.9 翻车急救手册核心 · 建议截图保存

你遇到的报错 / 状况原因 & 解法
! [rejected] ... fetch first
push 被拒绝了
远程有你没有的新提交(同事推过东西了)。
解法:git pull --rebase 拉下来合并好,再 git push
切分支时报
Your local changes would be overwritten
你有没提交的改动,切过去会被覆盖。
解法:要么 git commit 提交,要么 git stash 先收进抽屉。
detached HEAD
"我怎么不在任何分支上了?"
你 checkout 到了某个具体提交上(HEAD 没贴在便利贴上,飘着呢)。
解法:想保留改动就 git switch -c 新分支名;不想要就 git switch main 走人。
不小心把分支删了 git reflog 找到删除前那个提交 ID,然后 git branch 分支名 那个ID 重建。
合并到一半想反悔 git merge --abort / git rebase --abort,一键回到操作前。
把密码/密钥提交上去了 第一时间去重置那个密钥(最重要),再用 git filter-repo 或 BFG 清历史,然后通知全组重新 clone。
提交到错误的分支了 在错误分支上 git reset --soft HEAD~1 撤回提交(改动还在),git stash,切到正确分支,git stash pop,重新提交。
本地彻底乱了,只想跟远程一模一样 git fetch origin 然后 git reset --hard origin/main
⚠️ 本地未提交的改动全没,先确认没有要留的东西。
🛑 三条"打死也别做"的事

① 别在公共分支(main / develop)上 git push -f(强推)。这会直接覆盖掉服务器上的历史,同事的代码可能凭空消失。真要强推也请用 --force-with-lease,它至少会在别人有新提交时拦住你。

② 别把 node_modules、编译产物、密钥提交上去。

③ 遇到看不懂的报错,别乱试命令,先截图问人。Git 里绝大多数问题都能救回来,但乱敲一通之后就不一定了

04工具:怎么让 Git 更省事

命令行必须会(出问题时只有它能救你),但日常干活该用图形界面就用。会命令行 + 用图形界面 = 最舒服的状态。

4.1 图形化工具核心

工具价格大白话点评
VS Code 内置
+ GitLens 插件
免费实习生首选。左边栏点点点就能 add / commit / push,冲突有可视化界面。GitLens 插件能在每行代码旁边直接显示"这行是谁、什么时候改的",查历史神器。
JetBrains 系
IDEA / PyCharm / WebStorm
付费Git 集成是同类里最强的,三向合并冲突界面尤其好用。用 IDEA 的同学不用装别的了。
Sourcetree免费老牌独立客户端,分支图画得漂亮,适合用来"看清楚仓库现在是什么形状"。
Fork / GitKraken付费为主颜值和体验都很好,个人使用可以考虑。
GitHub Desktop免费最简单,功能也最少。纯新手过渡期用可以。
lazygit免费终端里的图形界面,键盘党的爱。习惯了效率极高。

4.2 让命令行更好用的配置进阶 · 但强烈建议抄走

# 常用命令起别名,少敲很多字
git config --global alias.st "status -sb"
git config --global alias.co "checkout"
git config --global alias.br "branch"
git config --global alias.cm "commit -m"
git config --global alias.unstage "restore --staged"

# 神级别名:一行一条、带颜色、带分支图的漂亮 log
git config --global alias.lg \
  "log --graph --pretty=format:'%C(yellow)%h%Creset -%C(auto)%d%Creset %s %C(dim)(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

# 以后就可以:
git st        # = git status -sb
git lg        # = 那条又长又好看的 log

4.3 值得知道的周边进阶

  • gitignore.io / GitHub gitignore 模板 —— 输入你的技术栈(Java、Node、Python…),自动生成一份靠谱的 .gitignore,别自己硬写。
  • pre-commit 钩子(husky / lint-staged / pre-commit)—— 提交前自动跑格式化和 lint,把"代码风格"这种争论从 Code Review 里彻底赶出去
  • CI 流水线(GitHub Actions / GitLab CI / Jenkins)—— PR 一提交就自动跑测试和构建,红了就不让合。
  • git bisect —— "这个 bug 是哪次提交引入的?" 用二分法几步就能揪出那次提交,排查历史问题的大杀器。
  • git blame —— 看某一行代码是谁写的。注意用它是为了「找人问清楚当时的上下文」,不是为了追责。

05命令速查表

这一页建议截图存手机,或者打印贴在工位上。

日常必备(每天都用)

命令作用
git status看当前状态(有疑问就敲它,无副作用)
git diff / git diff --staged看工作区 / 暂存区的具体改动
git add .把改动放进暂存区
git commit -m "说明"提交到本地仓库
git log --oneline --graph --all看提交历史和分支图
git pull / git pull --rebase拉最新代码
git push推到远程

分支操作

git branch / git branch -a看本地分支 / 看全部含远程分支
git switch 分支名切换分支
git switch -c 新分支名新建并切换
git merge 分支名把指定分支合并到当前分支
git rebase main把当前分支"垫"到 main 最新之上(仅限自己的分支)
git branch -d 分支名删除已合并的分支
git push -u origin 分支名第一次推送新分支

撤销与后悔

git restore 文件丢弃工作区改动 ⚠️不可恢复
git restore --staged 文件把文件移出暂存区(改动保留)
git commit --amend修改最后一次提交(说明或内容)
git reset --soft/--mixed/--hard HEAD~1撤销提交,三档力度递增
git revert 提交ID用一次新提交来抵消旧提交(公共分支专用)
git reflog查 HEAD 走过的所有足迹 —— 终极后悔药
git merge --abort / git rebase --abort放弃正在进行的合并/变基

临时保存与查询

git stash push -m "说明"把当前改动收进抽屉
git stash list / git stash pop查看抽屉 / 取出最近一个
git blame 文件看每一行是谁、哪次提交改的
git show 提交ID看某次提交的完整内容
git bisect start二分查找是哪次提交引入了 bug

06给新人的六条忠告核心 · 收尾

  1. 养成"敲 git status"的条件反射

    不知道自己现在处于什么状态,就敲 git status。它没有任何副作用,而且 Git 会在输出里直接告诉你下一步该干嘛。这是最便宜的安全带。

  2. 小步提交,别攒大招

    一个功能点提交一次,别攒一整天一次性提交 50 个文件。提交越小,出问题时越好定位、越好回滚,Review 的人也越愿意认真看。

  3. 提交信息写人话

    "三个月后的自己能看懂"是唯一标准。updatefix1 这种等于没写。

  4. 危险操作前先存档

    要 reset、rebase、force push 之前,先 commit 或 stash 一下。进过 Git 仓库的东西几乎都能捞回来,没进过的谁也救不了。

  5. 勤同步主线,别让分支长草

    每天 pull,分支活过一周就该警惕了。冲突的痛苦程度,跟你多久没同步成正比。

  6. 不确定就问,别硬猜

    Git 出问题时乱敲命令,很容易把"能救"变成"救不回"。截个图问一句,成本远低于恢复代价。顺便说:问问题不丢人,把代码搞没了才丢人。

🎯 最后一句

Git 的命令有几百个,但你日常真正会用到的不超过 20 个。别想着一次学完,先把「三个区域 + 分支是便利贴」这两个模型刻进脑子里,剩下的命令用到哪个查哪个,一个月就成肌肉记忆了。

07附:30 分钟讲解节奏建议

这一节是给你(分享人)自己看的,正式讲的时候可以跳过不展示。

时间讲什么怎么讲
0–3 分00 开场直接放"论文_最终版"那张图,问一句"有没有人干过这事"。先笑起来,再讲技术。
3–10 分1.3 三个区域
1.4 快照
1.5 分支是便利贴
本场最重要的 7 分钟。1.1/1.2/1.6 快速带过或直接跳。三个区域那张图一定要讲透,可以让大家跟着复述一遍"房间 / 纸箱 / 仓库 / 快递"。
10–18 分2.2 四连招
2.4 后悔药
2.5 分支
2.6 stash
建议开个终端现场敲 2.8 的实战流程,比念文档效果强十倍。2.4 那张后悔药表格是全场最实用的,多花点时间。2.3 / 2.7 一句话带过。
18–27 分3.2 pull/push
3.3 分支策略
3.4 PR 流程
3.6 冲突
冲突一定要现场演示一遍(提前准备好一个必冲突的仓库),让实习生亲眼看到那几行标记不可怕。3.7 rebase 只讲比喻和黄金法则,不展开。
27–30 分3.9 急救手册
06 六条忠告
快速过一遍急救表,让大家截图。忠告挑第 1、4、6 条重点说,收尾。
📌 现场小建议

① 提前准备一个演示仓库:预置好 main 和一个会冲突的分支,现场不用临时造数据。
② 留 3–5 分钟 Q&A,超时就把 3.7 rebase 和第 04 章工具部分砍掉,它们最适合当课后自读材料。
③ 分享结束把这份文档链接发群里,标了「进阶」的部分正好让大家课后自己翻。