日常开发中常会遇到的一些场景与对应操作命令
约 4409 字大约 15 分钟
其他
2025-07-04
初始化 Git
mkdir react-demo
cd react-demonpm create vite@latestgit init此时 Git 会默认创建一个分支(一般是 master,但是取决于你的 Git 版本和配置)
设置初始分支(推荐强制 main),现在主流远程仓库默认分支都是 main
把当前分支改成 main(推荐)
git branch -M main解释:如果当前分支是 master → 改成 main,如果当前分支已经是 main → 不影响,-M 代表强制重命名。
git add .
git commit -m "init project"如果你这里报错 user.name/user.email 没配置,就按下面做:
git config --global user.name "你的名字"
git config --global user.email "你的邮箱"在 GitHub / Gitee / GitLab 创建远程仓库,建议仓库创建时不要勾选初始化 README / LICENSE / .gitignore(否则远程会有一个 commit,你推送的时候会冲突,要先 pull/rebase)
你要创建的是一个 空仓库(Empty repository)
创建完仓库后,它会给你一个地址,一般是:
- HTTPS:
https://github.com/xxx/xxx.git - SSH:
git@github.com:xxx/xxx.git
设置远程地址(origin),把下面 URL 换成你仓库的:
git remote add origin <仓库地址>例如:
# github
git remote add origin https://github.com/yourname/react-demo.git
# gitee
git remote add origin https://gitee.com/yourname/react-demo.git
# gitlab
git remote add origin https://gitlab.com/yourname/react-demo.git推送到远程仓库(首次推送要加 -u),第一次推送 main 分支
git push -u origin main-u 的作用:以后你只需要 git push,Git 会记住默认推送到 origin/main
# 查看远程仓库信息
git remote -v
# 查看当前分支
git branch
# 查看提交记录
git log --onelineGit 重设远程地址
git remote -v重新设置远程地址,直接把 origin 改成正确的地址
git remote set-url origin <新的仓库地址>或者直接删掉再加:
git remote remove origin
git remote add origin <新的仓库地址>删除某些Git提交记录,但是保存本地更改
仅删除 Git 提交记录(但保留所有本地文件修改),比如删除 8f2c3b570 之前的所有提交记录,保留所有文件修改(工作区内容不变),生成一个新的初始提交(包含当前所有代码)。
步骤:
重置到目标提交(保留文件修改)
git reset --soft 8f2c3b570 # 回退到该提交,但保留所有文件改动重新提交所有文件(作为新起点)
git add . # 添加所有修改
git commit -m "Initial commit (after reset)"强制推送到远程(如需)
git push --force origin <分支名> # 谨慎操作!确保团队知晓效果验证
执行 git log:只会看到新提交 "Initial commit (after reset)"
查看旧版本代码
不会破坏当前代码
git checkout a1f904183b1b6d09d0f163a68e92faf105710ae2这样你会进入 detached HEAD 状态,即“游离状态”。
代码会回到那个 commit,你可以随意查看、运行。
此时你不是在任何分支上。
查看完后,如果想回到之前的分支:
git checkout main或者 master,取决于你的分支名字
版本回退
如果你确认要让分支历史回到那个 commit,有两种方式:
保留之后的提交(安全)
git revert a1f904183b1b6d09d0f163a68e92faf105710ae2..HEAD会逐个生成新的反向提交,等效于撤销从那时到现在的修改。
提交历史仍然保留,不会丢数据。
彻底回到那个版本(危险)
git reset --hard a1f904183b1b6d09d0f163a68e92faf105710ae2强制把当前分支指针移到那个 commit,之后的提交会 全部丢弃。
如果已经推送到远程,还需要:
git push origin main --force开启仓库级大小写敏感
git config core.ignorecase false然后为了让 Git 正确认出大小写变化,你需要再提交一次文件名更改。
比如你想把:
loading.svelte改成:
Loading.svelte你要这样操作:
git mv loading.svelte temp.svelte
git mv temp.svelte Loading.svelte
git commit -m "Fix filename case"这样 Git 就会记录大小写差异。
必须通过 git mv 触发 Git 记录文件名变更,否则 Git 仍然认为这是同一个文件 → 不提交。
临时告诉 Git 检查大小写变化
如果你不想重命名两次,可以用:
git mv -f loading.svelte Loading.svelte
git commit -m "Fix case"-f(force)可以强制重命名,即使文件名只有大小写不同。
Windows 文件系统(NTFS)本身不区分大小写
即使你在 Git 开启大小写敏感,你的系统仍然不敏感。
但 Git 可以在内部记录大小写变化,不影响最终推送到 Vercel(Linux)。
关闭 TypeScript 的 noUnusedLocals / noUnusedParameters
避免 tsc -b 因为 未使用的 import / 变量 / 参数 报 TS6133 并导致构建失败(尤其是 CI)。
tsconfig.json:
{
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false
}
}优点:简单粗暴,一步到位
缺点:开发阶段也不再检查 unused(但可以交给 ESLint 管)
只在 build 阶段关闭(开发仍严格)
新建 tsconfig.build.json:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false
}
}修改 package.json 的 build:
"build": "tsc -p tsconfig.build.json && vite build"优点:
- 本地开发继续严格提示 unused
- 部署/构建不因为 unused 挂掉(更工程化)
新分支开发+合并
项目里从主分支开新分支 → 开发 → 合并回主分支
假设主分支叫 main,你要做一个功能 feature/login:
从主分支拉最新,然后开新分支
git checkout main
git pull
git checkout -b feature/login在新分支开发(会产生多条提交)
git add .
git commit -m "feat: add login form"
# ...继续开发
git commit -m "fix: handle empty password"开发完成:合并回 main(合并前先让 main 最新)
核心点:合并时“不是挑某一条提交”,而是把你这个分支里“相对 main 新增的所有提交/变更”合并进去。
合并步骤通常是:
git checkout main
git pull
git merge feature/login
git push相关信息
如果 merge 后,想马上撤回,并且还没 push
直接在 main 上:
git checkout main
git reset --hard ORIG_HEADORIG_HEAD 通常就是合并前 main 的位置,这条命令会把 main 直接回到 merge 之前的样子。
--hard 会丢掉工作区/暂存区里未提交的改动(如果你合并后又改了文件但没提交,这些会没)。
想先确认一下会回到哪里:
git log --oneline --decorate -5如果你已经 push 到远端了(不要用 reset 强推,除非你确定没人拉过)
这种情况下推荐用“反做一次合并”(revert merge),更安全:
先找到那次 merge 的提交 hash
git checkout main
git log --oneline --decorate --graph -10会看到类似:commit xxxx (HEAD -> main) Merge branch 'feature/home-view' ,记下那个 xxxx。
反向撤销这个 merge(保留 main 这边作为主线)
git revert -m 1 xxxx然后再:
git push这会生成一个新的提交,用来“撤销合并带来的改动”,历史不会乱,别人也好同步。
开发分支中有多条记录,合并时的选择:
默认合并:合并的是“分支上新增的一切”
Git 会计算:feature/login 相对于 main 多了哪些提交(或者说多了哪些改动),然后把这些改动合进来。
方式 1:普通 merge(保留所有提交)
优点:信息完整,最直观
缺点:主分支提交历史可能很多、比较碎
git checkout main
git merge feature/login方式 2:Squash 合并(把多条提交压成一条)
含义:你分支里可能 10 个 commit,合到 main 变成 1 个 commit
优点:主分支历史干净
缺点:主分支上看不到你分支内部每一步的细碎提交(但分支本身还在的话仍能看到)
git checkout main
git merge --squash feature/login
git commit -m "feat: login feature"很多团队会在 PR / MR 上做 “Squash and merge”。
方式 3:Rebase(整理提交,再合并)
用于:你想在合并前,把分支提交“改写得更漂亮”(合并前整理提交顺序、合并一些小提交等)
注意:会改写历史,如果分支已被多人共享要小心
如果“开发完要合并时,主分支已经更新了”(main 有新提交)怎么办?
路线 A(最常用、最稳):在你的功能分支上把 main 合进来
思路:让 feature/login 先吸收 main 的最新变化,再合回 main。
git checkout feature/login
git fetch
git merge origin/main
# 解决冲突(如果有)
git checkout main
git pull
git merge feature/login特点:
不改写历史(安全)
历史里会多一个“merge main into feature”的合并提交
路线 B(更干净的历史):rebase 到最新 main 上
思路:把你的功能分支“挪到”最新 main 后面,让你的提交看起来像是基于最新 main 开发的。
git checkout feature/login
git fetch
git rebase origin/main
# 解决冲突(如果有),然后继续 rebase
# git rebase --continue然后再合并:
git checkout main
git pull
git merge feature/login特点:
- 历史线性、干净
- 会改写
feature/login的提交 hash(如果你已经 push 且别人基于你的分支开发,会麻烦)
如果你 rebase 过、并且这个分支你已经 push 到远端了,通常需要:
git push --force-with-lease(--force-with-lease 比 --force 更安全)
会不会冲突,取决于“你和主分支是否改了同一块内容”。
常见冲突场景
- 你改了
app/config.json,主分支也改了同几行 - 你和别人都改了同一个函数、同一个文件同一段
- 你删了一个文件,别人改了它
不冲突的常见情况
- 你改 A 文件,主分支改 B 文件
- 同一文件但不同区域(Git 通常能自动合并)
当你 merge / rebase 时出现 conflict,Git 会提示哪些文件冲突了。
看状态 git status,打开冲突文件,会看到这种标记
<<<<<<< HEAD
主分支内容
=======
你的分支内容
>>>>>>> feature/login你要做的是:决定保留哪边,或手动融合,然后删掉这些标记。
场景
你开始开发时:
A---B (main)
\
C---D (你的 feature)后来同事先完成并合进 main(main 多了 E、F):
A---B---E---F (main)
\
C---D (你的 feature)此时你的分支还是从 B 那里分出来的。
你在自己的分支上执行:
git checkout feature
git rebase mainGit 做的是:
- 先找出你分支“比 main 多出来的提交”——这里是
C、D - 把
C、D暂时“摘下来” - 让你的分支指针先移动到最新 main(到 F)
- 再把
C、D一个一个重新应用到 F 后面
结果变成:
A---B---E---F---C'---D' (你的 feature)
(main)注意:C' D' 不是原来的 C D,而是重新生成的提交(hash 会变)。
rebase 的效果通常就是让历史看起来像一条直线:
- rebase:线性(A-B-E-F-C'-D')
- merge main into feature:会出现一个合并节点(历史会分叉再汇合)
git rebase main 默认是:保留你分支里的每一条提交(只是换了“基底”,并且提交被重写成 C'、D')。
所以如果你原来有 10 个 commit,rebase 后还是 10 个 commit(只是 hash 变了)。
rebase 的冲突的本质跟 merge 一样,只要你和同事(main 上的 E/F)改到了同一段内容,就可能冲突。
区别只在于:
merge main:冲突发生在“合并那一下”rebase main:冲突发生在“重放你的每一个提交(C、D……)”的过程中
rebase 是把你的提交 一个一个重新应用到新地基上:
- 应用 C 时可能冲突一次
- 解决完继续
- 应用 D 时可能又冲突一次
所以你会感觉它更“碎”,但也有好处:你能更精准地知道是哪一个提交引入了冲突。
rebase 只是“更新你的开发分支”(让它基于最新 main)
rebase 的结果是:
- 你的
feature分支变成基于最新 main 的一串提交(线性) - main 分支本身完全没变
也就是说,rebase 并没有把你的代码“放进 main”。
你最终还是要做一次“把 feature 合进 main”的动作(通常是 merge / PR / MR):
git checkout main
git pull
git merge feature # 这一步通常会非常顺滑,甚至 fast-forward
git pushrebase 后合并通常更简单,因为你已经把 main 的 E/F 吸收进来了,冲突大概率已经在 rebase 时解决过了。
总结:
- rebase = 把你分支上“独有的提交”搬到新的 base 上
- main 的提交(E/F)不动,它们已经在那儿
- rebase 过程中可能冲突,你要当场解决
- rebase 完以后,你的 feature 只是“更干净、更接近能合并的状态”,但还没进 main
最实用的 rebase 冲突处理流程
git checkout feature
git fetch
git rebase origin/main
# 如果冲突:
# 1) 手动改文件
# 2) git add 冲突文件
git rebase --continue
# 重复直到结束想取消:
git rebase --abort两种做法的目标都是:让你的功能分支包含 main 的最新变化,这样最后合回 main 更顺。
merge main into feature:通过“合并”把两条历史接起来(会产生一个 merge commit,除非 fast-forward 这种特殊情况)
rebase feature onto main:把你的提交从旧基底“搬到”新基底上(重写提交,不新增 merge commit)
路线 A:merge main → feature,然后 feature → main
- 第一次:
feature吸收main(merge origin/main),通常会产生一个 merge commit(记录“我吸收了 main”) - 第二次:
main合并feature(merge feature),也可能产生 merge commit(取决于合并策略和历史形状)
不过要注意:第二次合并到 main 时,有时可能是 fast-forward(不会新产生 merge commit),但很多团队会强制产生 merge commit(PR merge commit 模式)。
rebase 不会产生“merge commit”(没有那个“合并节点”),但它不是“不会产生新记录”,而是:
- 它会把你的提交
C D重放成C' D' - 所以会产生新的提交对象(新 hash)
- 只是这些提交看起来还是你原来那几条提交(数量一样、内容相近),不像 merge 那样多出一个“合并提交”
所以更准确的表述是:
rebase 不新增 merge commit,但会重写你分支上的提交。
对比:历史长什么样
假设 main 上同事有 E/F,你有 C/D。
merge main into feature(会出现“吸收 main”的记录)
A---B---E---F (main)
\ \
C---D-----M (feature) # M 是 merge commit:把 main 合进 featurerebase(没有 merge commit,但你的提交被“重写”)
A---B---E---F---C'---D' (feature)
^
(main)最后合回 main:
- merge 路线:main 合 feature,可能又多一个 merge commit(看策略)
- rebase 路线:通常可以 fast-forward 或者合并很干净
总结:merge 是“保留真实发生过的分叉与汇合”,rebase 是“把自己的提交搬到最新主干上,让历史看起来像一直在主干上开发”。
这个情况 通常不会产生 merge commit,主分支会直接变成 A-B-C-D-E-F——这叫 fast-forward(快进)合并。
场景
- 主分支是:
A---B---C---D(main) - 你的分支是:
A---B---C---D---E---F(feature)
也就是说:feature 完全是沿着 main 往前走的,没有分叉(main 没有你没有的提交)
这时你在 main 上做:
git checkout main
git merge featureGit 会发现:main 只是 feature 的祖先,合并不需要“创造一个新节点来汇合两条线”,于是直接把 main 指针“快进”到 F:
A---B---C---D---E---F (main, feature)所以 没有 merge commit。
什么时候会有 merge commit?
只要出现过“分叉”——比如:
- main 有你没有的提交(同事先合进 main)
- 或者你在 feature 上 merge 过 main(产生了那个 M)
- 或者仓库设置/团队策略强制
--no-ff
那 Git 就不能简单快进了,往往会生成 merge commit。
实用的补充
有些团队会在合并时强制保留分支痕迹:
git merge --no-ff feature即使能 fast-forward,也会强制生成一个 merge commit。
很多时候 rebase 后再把 feature 合回 main,确实很容易变成 fast-forward。但有一个关键前提 + 两个常见例外
当你在 feature 上 rebase main 之后,历史通常变成:
A---B---E---F (main)
\
C'---D' (feature)如果这段时间 main 没再前进(还是停在 F),那你合并:
git checkout main
git merge feature就只是把 main 指针从 F “快进”到 D':
A---B---E---F---C'---D' (main, feature)没有 merge commit,也就是 fast-forward。
冲突往往会在 rebase 重放提交时解决掉,最后合回 main 会更顺。
例外 1:rebase 完之后,main 又有新提交了
你 rebase 的时候 main 在 F,结果你还没合并,main 又变成 G/H 了:
A---B---E---F---G---H (main)
\
C'---D' (feature)这时合并就不能 fast-forward(main 不是 feature 的祖先),你得:
- 要么再 rebase 一次到最新 main(H)
- 要么 merge(可能产生 merge commit)
例外 2:团队/平台强制“不要 fast-forward”
比如:
- 命令行用了
git merge --no-ff - GitHub/GitLab 设置为“总是创建 merge commit”
- 或者你们的合并按钮选的是 “Create a merge commit”
那即使可以 FF,也会强行生成一个 merge commit。
总结:rebase 让你的 feature 看起来像 main 的“直接后续”,所以只要 main 没再动、且不强制 no-ff,合并就基本是 fast-forward。
rebase + squash
rebase:让你的分支基于最新 main(提前解决冲突、减少合并时翻车)
squash 合并:把你分支上一堆零碎提交压成 1 条进入主分支(主分支历史干净)
git checkout feature/login
git fetch
git rebase origin/main如果有冲突:解决 → git add → git rebase --continue
git checkout main
git pull
git merge --squash feature/login
git commit -m "feat: login"
git push这样主分支只会多 1 条提交(你的“最终成果”),不会出现你那堆 wip/fix/try again。
rebase + squash 的关系
- rebase:不减少提交数量(默认),只是让提交“搬家”、历史线性、冲突更早解决
- squash:把这些提交“压扁”成 1 条进入 main