每个写代码的人,早晚都要面对版本管理这件事。刚开始我不太在意,直到有一次熬夜改了三天代码,因为一次误操作把整个项目覆盖,才真正体会到版本管理的分量。后来把Git当成每日必用工具,才发现真正高频的“常用命令”就二三十条,但每一条用对了都能省下大把时间。这篇东西适合刚接触Git的新人,也适合那些已经会用图形界面但说不清底层逻辑、遇到问题只能靠搜索引擎的人。我会把命令背后的“为什么”也讲清楚,这样以后遇到再奇怪的报错,你也能有个判断方向。
1. 先搞清楚Git的设计逻辑,再动手记命令
1.1 三个区域:工作区、暂存区、版本库
刚开始学Git,最容易懵的就是三个名词:工作区、暂存区、版本库。我见过不少新人把 git add 和 git commit 混在一起理解,以为 add 就是保存。其实整套模型可以拿写文章投稿来类比。
工作区就是你电脑里能看到、能编辑的那些文件夹,相当于你的草稿纸;暂存区是一个临时存放清单的区域,相当于你把已经修改好的段落贴到“待发表”栏;版本库则是已经保存下来、带编号的历史记录,相当于正式发布存档。git add 是把改动放入暂存区,git commit 才是把暂存区的改动正式写入版本库,生成一个不可篡改的历史快照。
这套设计不是平白无故多出来的环节。有了暂存区,你可以把一次改动拆成多个逻辑提交。比如我改了一个页面样式,顺带修了一个接口参数,两个改动混在一起时,就可以分别 add 不同的文件,分两次 commit。提交记录干净了,后期排查问题和回滚都会轻松很多。
1.2 一条命令的完整执行路径
看透一次提交流程,对理解很多疑难杂症很有帮助。假设你改了一个文件,从最开始的编辑动作到最后被保存进版本库,实际经历了下面这几步。
工作区里的文件被编辑器修改,但Git还感知不到具体内容变化,只能看出“有改动”。随后执行 git add,这相当于把文件内容的一次快照放进了暂存区。最后 git commit,Git 会把暂存区的这份快照连同提交时间、提交人、提交信息一起打包成一个 commit 对象,挂到当前分支上。提交对象里还记录了“父提交”的指针,指向本次提交发生前的那个历史节点,这样就形成了一条链式历史。
很多人对 Git 的理解停留在“保存版本”的层面,觉得 commit 就像文件另存为。其实它更像是在一条时间轴上不断追加节点。每个节点都有自己的哈希值,节点之间通过父子关系串联。理解了这条链,之后接触 reset、rebase、cherry-pick 时就能明白它们到底在“动”什么。
1.3 .git目录里到底存了什么
在你的项目根目录下有个隐藏文件夹叫.git,那是仓库的心脏。平时操作无非是在生成、调整和读取这个目录里的数据。它里面有几个关键部分:HEAD文件记录了当前处于哪个分支;config文件保存了仓库级配置;refs目录下面存放分支和标签的引用信息;objects目录是真正的数据仓库,各种对象都压在里面。
理解了.git目录,你就理解了一个经典问题:为什么直接用系统复制粘贴一个项目文件夹,到另一台电脑上就不是一个仓库了?因为你拷贝的可能只有业务代码,并没有涵盖.git里面的全部元数据。合理的迁移方式是直接复制整个项目,或者用git clone从远程仓库拉取,这样才是一个完整仓库。
1.4 注意:不是所有文件都在版本控制里
这里要说个容易踩的坑。.git只跟踪它看得见的东西,默认情况下新建目录和文件并不会自动纳入版本控制。很多新人编译运行项目后,发现一堆中间产物被误提交到仓库里,就是因为忽略了这一点。后面讲.gitignore的时候我会再展开,但先记住一个结论:Git 的跟踪对象是你明确 add 过的文件或匹配规则的文件,不存在的文件它根本不管。
2. 环境准备与命令行基础配置
2.1 安装Git与初识命令行
Git 本身是跨平台的工具。在 Windows 上,官方安装包或者常见的包管理器都可以装;macOS 和 Linux 也基本都能通过系统自带包管理器快速搞定。装完之后,先在终端里执行git --version确认一下版本,太老的版本可能会导致某些命令不兼容,尤其是分支切换相关的命令。
说句实在话,图形客户端确实容易上手,但命令行才是理解 Git 的最佳途径。因为每个命令在做什么,图形界面通常只显示了结果,而命令行会把完整的操作逻辑暴露给你。我也不是让你完全抛弃图形工具,而是建议先尽量用命令行把这条路走通,等到心中有模型之后再借助工具提升效率。
2.2 全局配置:身份、编辑器与默认行为
刚装好 Git,第一件事是配置身份信息,否则 commit 时会报错或者提示无法确认提交人。在我的机器上,写代码的机器和提交代码的机器可能会分开,所以全局身份信息用工作常用的那个邮箱和昵称就好。
git config --global user.name "你的昵称" git config --global user.email "you@example.com"除了身份,还有几个推荐设置。可以指定默认编辑器,比如 Vim、VS Code 或者你习惯用的任意编辑器,这样当 git 需要你输入多行提交信息时会自动弹出来。还有很多人会设置默认分支名,比如把init.defaultBranch设为main,避免每次手动去改名。
核对配置用git config --list,它会显示所有生效的配置项。配置优先级从高到低大致是仓库级、全局级、系统级。也就是说,如果某个项目里单独设置了 user.email,那么在这个项目里提交时会优先用项目级的配置。这个优先级我记得很清楚,因为曾经有同事在仓库里配了一个错误的邮箱,导致所有提交都记录在了错误的身份下,排查了半天才找到原因。
2.3 SSH公钥配置与远程认证
如果你打算把代码同步到云端仓库,推荐使用 SSH 方式连接。相比每次输账号密码,SSH 的体验稳定很多,身份认证也更安全。生成密钥的时候直接敲下面这条命令:
ssh-keygen -t ed25519 -C "you@example.com"生成后,公钥默认在~/.ssh/id_ed25519.pub文件里。打开这个文件复制全部内容,去你的代码托管平台个人设置里找到 SSH Keys 配置页,粘贴保存。验证是否连通,可以执行:
ssh -T git@example.com如果连接成功,会返回一段欢迎提示。这里我要提醒一句:托管平台返回的标识信息可能与你注册的用户名不一致,只要能看到成功提示,说明密钥已经生效。千万不要把这个验证流程误解为登录,它只是检查认证是否通过,具体操作还是要靠git命令完成。
3. 日常提交:最开始会用到的那几条命令
3.1 提交前的体检:status 与 diff
写代码时最好养成一个习惯:提交前先看一眼仓库当前状态。git status是最常用的命令,它告诉你当前分支、暂存区状态、哪些文件被修改但还没 add,以及哪些文件完全没有被跟踪。我把它理解成“提交前的体检报告”。
光是 status 只能看出文件级别有没有变化,具体的改动内容要靠git diff来看。不带参数时,git diff比较的是工作区和暂存区之间的差异,也就是你上一次 add 之后又改了哪些内容。想看暂存区与版本库之间的差异,用git diff --cached。我每次提交前都会执行一遍git diff --cached,逐行确认这次将进入历史的改动确实是自己想要的,而不是把调试日志、临时代码一起提交进去。
3.2 add 的讲究:从整包加法到逐块挑选
git add最朴素的使用方式是添加指定文件或目录,比如git add src/main.js。偷懒一点可以直接git add .,把当前目录所有改动加入暂存区。我建议在项目较小、改动集中时用git add .没问题,但改动特别多、包含临时文件时就不要无脑全加了。
更精细的做法是git add -p,它会把文件里的改动按“块”拆开,让你逐个决定要不要暂存。比如同一个文件里既有需求代码又有格式化改动,你想把后者排除掉,用-p就能干净地把两者拆开提交。这个过程刚开始会觉得繁琐,但一旦养成习惯,你的提交历史会非常漂亮,每一笔 commit 都是完整且语义明确的。
如果你不小心 add 了不该加入的文件,可以用git rm --cached <file>把它从暂存区移除,同时保留工作区文件。切记不要直接git rm,那会连磁盘上的文件一起删掉,这不是你想达到的效果。
3.3 commit 的规范:信息写清楚比写多更重要
提交信息的质量,直接决定日后你(以及你的同事)排查问题的效率。我见过太多人提交信息只写“update”、“fix”,等到一个月后回看历史,完全不知道那次修改在干嘛。写提交信息并不需要长篇大论,但至少要让人一眼看懂这次改动做了什么。
如果只是简短的描述,可以直接git commit -m "修复订单金额计算精度问题"。如果需要补充背景信息,不要带-m,直接执行git commit进入编辑器,写一行标题,空一行再写正文说明原因和影响。
如果发现上一次提交的说明写错了,或者漏了一个文件,可以用git commit --amend修正上一次提交。它会把当前暂存区的内容与上一次提交合并成一个新提交,并允许你重新编辑说明。这里有一条铁律:只对还没有推送的提交使用 amend。如果已经推到了公共分支,改写历史会严重影响其他人的工作。
3.4 查看历史:log 与 show 的正确打开方式
git log是查阅提交历史的基础命令,默认输出完整哈希、作者、日期和提交信息。信息量太大时会刷屏,我常用的组合是git log --oneline --graph --all,一行一个提交,并带上分支图形。加-n 5可以只看最近五条记录。
想回顾某个提交到底改了哪些文件、哪些行,可以执行git show <hash>。这比盲目点开图形界面去翻版本树要快得多。如果想知道某一行代码最近一次是哪个提交引入的,用git blame <file>就能逐行显示来源。过去排查线上问题时,git blame帮我快速锁定过不少“罪魁祸首”,这是 Linux 服务端开发里非常刚需的操作。
4. 分支操作:多人协作的地基
4.1 分支的模型与常用命令
分支是 Git 里最重要的概念之一,简单说,它只是一个指向某个提交的可移动指针。开发新功能时从主线拉一条分支出来,在主线上继续发布其他内容,功能开发完再合并回去,彼此互不干扰。
创建分支可以用git branch <name>,切换分支可以用git checkout <name>。更推荐新版本的用git switch <name>,因为 switch 语义明确,只做切换。创建并切换一步到位用git switch -c <name>,等价于过去的git checkout -b <name>。
删除分支时,git branch -d <name>只删除已经合并进当前分支或主线上的分支,是个安全操作;git branch -D <name>则是强制删除,不管分支内容是否合并。我的建议是,本地分支删除前先用git branch -d,如果报错说分支未合并,那就先确认该分支的内容确实不需要保留,再考虑用大写-D强制删除。
4.2 合并与变基:merge 和 rebase 的选择逻辑
合并分支有两个主流方式:merge 和 rebase。两者的目标都是把另一条分支的提交内容整合到当前分支,但结果的历史形态不一样。
merge 会保留分支分叉的痕迹,执行后继提交时会在历史里增加一个“合并提交”节点。适合在共享分支上汇合持续开发的功能分支,因为保留真实的分叉和汇合关系,让团队能清晰看到“什么时候做了什么合并”。rebase 则相反,它会把当前分支的提交“搬移”到目标分支的最新提交之后,历史变成一条直线,看起来干净工整。
适合场景可以参考这张表:
| 对比维度 | merge | rebase |
|---|---|---|
| 历史形态 | 保留分叉和合并节点 | 线性推进,无合并节点 |
| 适用分支 | 公共分支、长期分支 | 个人开发分支、推送前的整理 |
| 安全性 | 公共分支上使用安全 | 推送到公共分支的提交不要用 |
| 冲突处理 | 一次性处理,多提交可以整体合并 | 逐提交重放,冲突可能反复出现 |
我的个人习惯是:个人功能分支在合并到主线之前,先用 rebase 整理掉那些“补个逗号”“修正上一条提交”的临时提交,让主线历史尽量干净。可是提交一旦推送到公共分支,我绝不再对这个分支做 rebase,否则所有人本地仓库的历史就会分叉,后续 pull 会出现一堆莫名其妙的冲突。
4.3 解决冲突的标准流程
冲突并不少见,也并不可怕。当两个分支修改了同一文件同一块区域时,Git 不知道谁是对的,于是把决定权交给你,并在文件里标出冲突段落。
一段典型的冲突标记长这样:
<<<<<<< HEAD 这边是当前分支的代码 ======= 这边是合并进来的分支的代码 >>>>>>> feature/login处理冲突时,我自己的标准流程是:先执行git status看看有哪些文件处于 unmerged 状态,然后逐个打开文件,根据自己的业务判断保留需要的代码。直接粗暴地全选“ours”或“theirs”往往不是好主意,因为两边可能各自改了一半逻辑,只有完整理解双方意图才能真正解决冲突。改完之后执行git add <file>标记为已解决,最后再提交。
rebase 过程中的冲突解决方案略有不同。git rebase遇到冲突会暂停在出问题的那个提交上,处理完文件后要执行git rebase --continue,继续重放下一个提交。某次实验发现整个 rebase 思路错了想放弃时,执行git rebase --abort就能回到操作前的状态。
5. 远程仓库:让代码真正“上云”
5.1 remote、fetch、pull、push 的关系
本地仓库是你的工作现场,远程仓库才是团队协作的公共枢纽。要让本地仓库和远程仓库建立联系,先添加一个 remote:
git remote add origin <仓库地址>remote其实是远程仓库地址的别名,“origin”是默认命名。用git remote -v可以查看当前配置的远程地址。第一次推送时,如果远程分支还没建立,要加-u参数:
git push -u origin main-u表示设置上游关联,之后直接git push或git pull就可以,不必每次都带上远程仓库名和分支名。
fetch和pull的差别也很关键。fetch 只是从远程把最新提交下载到本地,但不会动你当前的工作区;pull 则相当于 fetch 加 merge,会直接把远程分支合并进当前分支。我遇到一些把握不准的场景时,会先git fetch,看两个分支的差异再决定怎么合并,避免 pull 直接合并带来的意外冲突。
5.2 push 报错的常规解法
推送时最典型的报错是“failed to push some refs”,意思是本地和远程分支出现了分叉,远程有本地没有的提交。这个报错的根源,是因为你把历史的“时间轴”改动了,通常是远程仓库里已经有别人推过新代码,本地却基于旧节点直接提交。
正确做法是先把远程更新拉到本地,再尝试推送。用git pull --rebase origin main,把本地提交先“搁置”起来,拉取远程新提交,再把本地提交重放到远程最新节点的后面。如果本地改动和远程改动没有冲突,整个过程一气呵成;如果冲突了,按前面讲过的冲突解决流程处理。
这里提醒两句:一是不到万不得已不要用git push --force。强制推送会覆盖远程历史,一旦操作失误后果很难挽回,建议用git push --force-with-lease代替,它会在推送前检查远程是否发生了预期外的变化,安全性高很多。二是你在本地做了 rebase、reset 等改写历史的操作后,推送到远程通常要用强推才能生效,这种情况下更要谨慎确认目标分支上只有你一个人在工作。
5.3 为什么更推荐 pull --rebase
我在团队里推荐默认使用git pull --rebase。原因是本地分支频繁从远程拉取更新时,如果用默认的 pull(fetch+merge),每次拉取都可能生成一个合并提交,时间久了历史里全是“Merge branch 'xxx' into xxx”这类噪音。而使用 rebase 方式,你所有的本地提交就像排在你拉取到的远程提交之后,历史干净,接近一条直线。
有个边界要反复提醒:rebase 适合在你自己的私有分支上做,不适合在共享的固定分支上做。如果团队里大家都遵守这条规则,历史就会非常清楚。如果已经推上去的提交还在处理中,那宁可先用 merge,也别为了“历史好看”去改写公共记录。
6. 回滚与修复:犯错了怎么办
6.1 reset 的三种模式:软、硬、混合怎么选
代码写错了、提交打错了,第一时间想到的是回滚。Git 里最基础的回滚命令是git reset,它有三种模式,对应你希望保留多少工作成果。
| 参数模式 | 作用范围 | 工作区保留与否 | 暂存区保留与否 | 典型场景 |
|---|---|---|---|---|
| --soft | 仅回退提交记录 | 保留 | 保留所有改动在暂存区 | 想重新拆分提交,但内容不想丢 |
| --mixed(默认) | 回退提交记录并清暂存区 | 保留 | 清空到工作区 | 想重写提交内容,重新 add |
| --hard | 回退提交并清空暂存区和工作区 | 不保留 | 不保留 | 确认改动彻底作废 |
举例说,git reset --soft HEAD~1会把最近一次提交从分支上摘掉,但你的改动全部保留且在暂存区;git reset --hard HEAD~1则是把最近一次提交连同工作区改动全部丢掉,慎用。进行 reset 操作前,我建议先git log --oneline确认你要回退到哪个节点,哈希错了后果会很麻烦。
reset 严格来说是针对尚未推到远程的提交的。如果你已经把提交推送到了公共分支,那么 reset 之后其他人那里还是旧历史,推上去会乱套。这种场景下应该用下一节说的 revert。
6.2 revert 与 reset 的本质区别
git revert不是“回退到某个节点”,而是“生成一个与目标提交相反的新提交”。比如你提交了 a 这个提交,revert a 会生成一个提交 b,b 的内容恰好把 a 的改动整体撤销。历史里同时保留 a 和 b,长期记录看起来更完整,也不会破坏其他协作成员对历史共同的认知。
什么时候用哪个,我总结得很简单:还没推送出去的错误用 reset 清理,已经推送到公共分支的错误用 revert 撤销。revert 的本质是向前走一步,不是把历史折返,因此团队协作场景里安全很多。
6.3 误删分支、误改文件后的自救
误删分支或者误执行了git reset --hard,不用立刻陷入绝望,git reflog往往能把你的操作记录打捞回来。reflog 是“引用日志”,记录了 HEAD 和各种分支在本地仓库里的每一次变更,包括你曾经切换过的分支、reset 之前所在的位置。
比如你误删了一个分支,先用git reflog找到那个分支最后一次提交的哈希,再执行git branch <分支名> <哈希>就能恢复。同理,误 reset 之后也可以用同样方法找回旧版本。要注意 reflog 的内容通常只会保留一段时间,一般默认是90天,而且本地 clone 下来的仓库 reflog 为空,所以发现问题要尽快处理。
git clean也是一个危险命令。它用来清理未跟踪文件,但git clean -fd会把未跟踪的文件和目录直接删掉,而且不能通过 Git 命令恢复。我在使用 clean 前会先加-n预演一遍,确认要删除的清单里没有重要文件才真正执行。
6.4 stash:临时切换现场的救命稻草
正在改功能 A,突然线上来了紧急 bug 要切分支修,改动还没提交,直接切换分支会报错或者把改动带过去。这时git stash就是最顺手的方案。
git stash # 把当前改动暂时收起 git stash list # 查看保存的 stash 列表 git stash pop # 恢复最近一次 stash 并删除该记录 git stash apply # 恢复最近一次 stash 但保留记录默认情况下 stash 不会保存未跟踪文件,如果你需要连新建的文件一起收起,用git stash -u。恢复 stash 时如果和目标分支有冲突,会像 merge 冲突一样要求手动解决。我一般会在一天内恢复 stash,放太久容易忘记当时改到哪一步,恢复时反而混乱。
7. 高频技巧:让命令组合出花来
7.1 cherry-pick:精准移植提交
有时候只想把其他分支上的一个或几个提交弄到当前分支,不想全局合并。比如紧急修复提交打在 release 分支上,希望 master 分支也修复同样的 bug,git cherry-pick <hash>就是干这个的。
它可以指定单个或多个提交哈希,把对应的改动在新分支上重放。如果害怕冲突,最好先确认目标提交与当前分支改动重叠不大。cherry-pick 产生的新提交会生成新的哈希值,时间、摘要都会标记为当前操作完成。如果你希望保留原提交的信息,可以在提交流程里不要填写新的信息,直接沿用即可。遇到冲突时处理方式和 merge 类似,解决完git cherry-pick --continue,想放弃就git cherry-pick --abort。
7.2 tag 打标签与版本发布
tag 是对某个提交做的一个固定标记,常用于版本发布的里程碑。比如你完成了 v1.2.0 版本的代码,希望以后任何时候都能快速切到这个版本,就可以打一个标签。
git tag v1.2.0 git tag -a v1.2.0 -m "发布 1.2.0 版本"轻量标签只是指向某个提交的指针,附注标签会额外保存打标签的时间、作者和说明。我推荐用附注标签,因为发布版本通常需要记录责任人、发布说明,轻量标签在多少天后往往被遗忘。标签需要单独推送:git push origin v1.2.0,如果要一次推送所有本地标签,可用git push origin --tags,但我的习惯是只推送明确需要的标签,避免把零散的本地实验标签传到公共仓库。
7.3 alias 别名配置
高频命令加上别名之后,日常操作舒服很多。Git 支持在配置里给命令起别名,也可以用一个别名触发一串组合命令。我维护的.gitconfig里有这样几个:
git config --global alias.st status git config --global alias.ci commit git config --global alias.lg "log --graph --pretty=format:'%h - %an, %ar : %s' --date=short"配好之后,git lg就能看到一张非常直观的提交关系图。别名本质上是字符串替换,它能帮你把冗余参数收敛成简短指令。新人阶段不要反着来——先熟悉完整命令,再配置别名,否则看到别人的别名配置会一头雾水。
7.4 .gitignore 的常见坑
.gitignore是仓库里的忽略规则文件,用来告诉 Git 哪些文件不纳入版本控制。编译产物、依赖目录、本地配置、IDE 配置等都应该忽略,否则仓库会越来越臃肿,还会把每个人的本地差异反复提交上去。
这里有两个高频踩坑点。第一,已经被 Git 跟踪的文件不会因为加了 ignore 规则就自动停止跟踪,你必须先git rm --cached <file>把它从版本控制里移除。第二,忽略规则的匹配方式和普通通配符不完全一样,比如build/表示忽略 build 目录,*.log表示忽略所有 .log 文件,但/foo只匹配根目录下的 foo。配置后建议用git status检查一下是否生效,有些目录结构复杂时需要调整规则。
8. 常见问题与排查实录
8.1 提交错分支怎么办
有次我在主线分支上写开发功能,写完才发现当前分支切错了,所有提交都落在了主线。这个问题可以按三步抢救。先基于当前 HEAD 创建一个新分支,把刚才的提交留在新分支里;然后回退主线分支的提交;最后切换到新分支去继续工作。
git branch feature/temp git reset --hard HEAD~1 # 根据提交数量调整 git checkout feature/temp这么做的本质是让“错误提交”从一个火坑挪到正确的位置。重要的是不要在回退之前 push 到远程,否则公共历史已经被污染,处理起来会复杂很多。
8.2 冲突中途想退出
merge 或 rebase 过程中,如果发现冲突过多、这条路走错了,不必硬着头皮一个个解决。merge 冲突可以用git merge --abort回到合并前的状态,rebase 冲突对应git rebase --abort。两者都会把工作区还原到操作开始前的样子,如果你在冲突期间手动改了好几个文件,这些改动也会一并回滚,所以执行前一定要确认自己没有想留的修改。
还有一个更温和的方案是查看冲突文件数量不多时,直接读完所有标记后一次性处理。只要流程熟练,大多数冲突其实十分钟内都能解决,abort 主要还是用在“方案选错”的场景。
8.3 代码被覆盖了还能恢复吗
git reset --hard之后,心里最慌的往往是“改动的代码丢了”。不要先急着重建文件,先去查git reflog。reflog 里记录了旧位置,你可以从那里把丢失的提交或分支找回来。我曾经在一个迭代收尾时误操作 reset 了刚写半天的代码,后来正是靠 reflog 恢复了现场。
真正没有退路的情况是同时执行了git clean -fd,把未跟踪文件也删了,Git 完全不知道这些文件的内容,恢复基本无望。所以在玩 clean 之前一定要预演,在玩 hard reset 之前一定要确认,能用 revert 就别用 reset。
8.4 常见错误信息速查表
平时被问到最多的问题,我整理成了一份速查表,遇到报错时先对照定位原因。
| 报错信息 | 原因 | 处理方式 |
|---|---|---|
| fatal: Not a git repository | 当前目录不是 Git 仓库 | 先 git init 或进入仓库目录 |
| refusing to merge unrelated histories | 两个仓库没有共同历史 | 确认分支正确后,可加 --allow-unrelated-histories,但慎用 |
| failed to push some refs | 本地落后于远程或历史分叉 | 先 git pull --rebase,再重新 push |
| Authentication failed | 认证方式错误 | 检查 SSH 密钥是否添加或 token 是否过期 |
| You have unmerged paths | 冲突尚未解决 | 处理冲突文件后 git add,再提交 |
| fatal: destination path already exists | clone 目标目录已存在同名项目 | 更换目录,或者确认已有仓库是否可以继续使用 |
再补充一个易误操作的点:遇到refusing to merge unrelated histories时,立刻去搞清楚两个仓库是怎么变成“无关联”的,盲目允许合并可能会让历史里凭空多出两棵互不相干的树,后续排查很麻烦。如果这个报错来自你故意把两个独立项目合并一起,那使用对应参数就是合理场景;否则更建议重新建仓库或者用 cherry-pick 精确迁移。
最后再分享一个小技巧:不管遇到什么问题,先git status总不会错。它会把当前工作区、暂存区、分支信息完完整整地展示出来,大多数问题在这个环节就能定位。我自己平时很少背完整命令手册,但每一条命令执行前都会想清楚它会影响哪个区域,也就不会因为误操作把仓库搞得一团糟。Git 这东西,真用熟了以后会觉得它像一把趁手的瑞士军刀,用得越多,越能体会到那些设计细节的妙处。