1. 动手之前先定性:你的本地文件夹到底处于什么状态
我前两天收到一条私信:“本地写了一堆代码,想跟 GitHub 上已经建好的仓库同步,折腾了一下午没搞定,git push 一直报错。”这个场景在开发者里面太常见了。很多人觉得本地文件夹和 GitHub 远程仓库同步,就是“把文件夹里的文件传到网上”,但实际操作起来会发现,Git 要同步的其实是“提交记录”,不是文件本身。文件只是提交记录在不同时间点的快照,所以只要两边的提交历史对不上,push 就必然失败。
如果你现在也卡在这个问题上,先别急着敲命令。动手之前花两分钟,搞清楚你手头的文件夹到底属于下面哪一种状态,后面的操作就顺理成章了。
1.1 用三条命令快速判断现状
在终端里进入本地文件夹,依次执行下面三条命令:
ls -la git status git remote -v第一条命令看有没有.git目录。注意ls -la里有一个.,这是用来显示隐藏文件的参数。如果文件夹里能看到.git,说明它已经被 Git 管理,是一个仓库;如果没有,那它目前只是一个普通文件夹,Git 还不认识它。
第二条命令git status会告诉你这个仓库的当前状态:有哪些文件没被跟踪、哪些文件改动还没提交、当前在哪个分支上。如果输出的是fatal: not a git repository,那就说明本地文件夹根本还没初始化。
第三条命令git remote -v用来查看已经关联的远程仓库地址。输出为空,说明本地虽然是仓库,但还没和任何远程仓库建立联系;有输出,说明本地仓库早就挂了一个远程地址——这时候要特别小心,它挂的可能不是你今天想同步的那一个。
这三条命令组合起来,基本就能把你的处境分成下面几种:
| 本地文件夹状态 | 典型特征 | 接下来主要做什么 |
|---|---|---|
| 普通文件夹,还没被 Git 管理 | 没有.git目录 | 初始化仓库,再对接远程,或直接复用远程仓库 |
| 已经是 Git 仓库,但没有远程地址 | git remote -v为空 | 添加远程地址,把两边历史接起来 |
| 已经是 Git 仓库,远程地址指向别处 | 已有 origin,但地址不是目标仓库 | 修改远程地址,或添加一个新的 remote |
| 本地和远程都有互不相关的提交 | 两边历史完全对不上 | 先拉取,再合并独立历史 |
我在实际帮人看问题的时候发现,绝大多数人卡住,不是因为命令不会敲,而是没意识到自己处在哪种状态,拿着别人的教程一顿照抄,最后把仓库搞得越来越乱。
1.2 为什么 push 会比想象中更容易报错
新手最常见的报错长这样:
error: failed to push some refs to 'git@github.com:user/repo.git' hint: Updates were rejected because the remote contains work that you do not have locally.这句话的意思是:远程仓库里有你本地没有的提交。你试图用 push 把本地的提交“盖”上去,但 Git 出于安全考虑,不允许你这么干,因为它担心你把别人(或者你自己之前在网页端提交)的内容悄悄覆盖掉。
这里要建立一个核心认知:git push不是“上传文件夹”,而是“把本地的提交记录补充到远程”。远程仓库如果有你本地没有的提交,你必须先把它拉下来,让两边的历史合并,然后才谈得上 push。这就好像两个人各自记了一本笔记,你想把自己的笔记合并进去,得先看看对方的笔记内容,而不是直接把自己的本子扔进去把对方的本子砸了。
所以,同步的核心动作链是:先获取远程的提交,再把本地和远程的历史合并,最后把合并结果推回去。如果这一步没搞清楚,后面所有的报错都像是无头苍蝇。
1.3 动手同步之前,先把本地改动安置好
无论你是哪种状态,同步之前一定要保证本地工作区是干净的,或者至少把没提交的改动暂时存起来。我的习惯是先跑一遍:
git status如果看到Changes not staged for commit或者Untracked files,说明有改动还没提交。此时如果你直接去 pull,Git 可能会拒绝执行,提示:
error: Your local changes to the following files would be overwritten by merge这句话的意思是:拉取下来的远程提交会覆盖你本地还没提交的修改,Git 不帮你做这种危险操作。正确处理方式是二选一:要么先把改动git commit提交,要么用git stash把它们临时藏起来,等同步完成后再git stash pop取回。我推荐小改动直接提交,大改动或半成品用 stash,这样后续处理冲突时压力小很多。
2. 本地文件夹还不是仓库:对接已有远程仓库的两条路线
如果你的本地文件夹连.git目录都没有,那事情反而简单。你面对的是一个“全新本地目录”和一个“已经存在的远程仓库”,要怎么让两边建立联系,有两条路线可以选。
2.1 路线对比:clone 复用 vs init 后拉取
第一条路线:把远程仓库 clone 到一个新目录,然后把你本地文件夹里的文件复制进去,最后在 clone 出来的目录里提交推送。第二条路线:在本地文件夹里直接git init,把它变成仓库,添加远程地址,再拉取远程内容进行合并。
这两条路线的核心差别在于“谁的历史成为基础”。clone 路线意味着你完全接受远程仓库现有的历史,本地文件夹只是往这个历史里追加一个新的提交,整个过程干净利落,基本不会出现“历史不相关”的报错。init 路线则保留了本地文件夹自身的仓库身份,再用--allow-unrelated-histories把两段历史硬拼在一起,适合你不想移动文件位置、或者本地目录路径已经被其他脚本引用的场景。
| 对比项 | clone 后搬文件 | init 后拉远程合并 |
|---|---|---|
| 是否需要移动文件 | 需要,文件会复制到新目录 | 不需要,原地操作 |
| 历史基础 | 以远程历史为基础 | 两段独立历史拼接 |
| 出现冲突的概率 | 低,只有同名文件覆盖问题 | 较高,需要处理 unrelated histories |
| 适合场景 | 远程内容已经比较完整,本地文件只是补充 | 本地文件很重要,希望保留自己的提交记录 |
2.2 远程是空仓库或只带 README 时的完整操作
先看最简单的场景:远程仓库刚建好,里面什么都没有,GitHub 页面上只有一个仓库名,没有 README,没有 .gitignore。这种时候本地直接初始化、提交、添加远程、推送,一气呵成:
cd 本地文件夹 git init -b main git add . git commit -m "首版代码提交" git remote add origin git@github.com:你的用户名/仓库名.git git push -u origin main这里有两个细节。第一,git init -b main里的-b main是指定初始分支名为main,这是为了和 GitHub 默认分支保持一致。如果本地仓库已经初始化成了master,后面可以用git branch -M main改过来。第二,git push -u origin main里的-u表示把本地main分支和远程origin/main分支建立跟踪关系,以后直接敲git pull和git push就可以,不用每次带参数。
但如果远程仓库在创建时勾选了“Add a README file”,情况就不一样了。远程那边已经有一个 README 提交,你本地又是一个全新的、没有任何历史的仓库,直接 push 必然报错。这时候要先拉取,再把两边历史合并:
git fetch origin git merge origin/main --allow-unrelated-histories--allow-unrelated-histories是这整篇文章里最重要的一个参数。Git 默认拒绝合并两段没有任何共同祖先的历史,因为你本地初始化产生的第一个提交和远程 README 提交互不认识。加上这个参数,就是明确告诉 Git:我知道这两段历史不相干,但我就是要强行把它们拼在一起,冲突我自己处理。
2.3 文件冲突时怎么取舍:保留本地的还是远程的
如果远程仓库里已有 README.md、.gitignore 等文件,而你本地也有同名文件,合并时就会产生冲突。Git 会把冲突标记写进文件里,你在文件里会看到类似这样的内容:
<<<<<<< HEAD 本地已有的 README 内容 ======= 远程拉下来的 README 内容 >>>>>>> origin/main手动编辑这个文件,把不需要的部分删掉,保留希望留下的内容,然后保存。但如果你明确只想要某一整份文件,不想一行一行改,可以直接用命令选择:
git checkout --ours -- README.md # 保留本地版本 git checkout --theirs -- README.md # 保留远程版本注意,在合并(merge)场景下,--ours指的是你当前所在分支的版本,也就是本地版本;--theirs指的是被合并进来的那个分支,也就是远程版本。很多人在这里搞反,因为直觉上觉得“远程是他们的”,其实是反过来想的。
处理完冲突文件后,执行git add README.md,然后git commit完成这次合并。此时本地分支的历史里已经包含了远程的 README 提交,再执行git push就顺理成章了。
2.4 如果你不想处理冲突:一条更省事的保底方案
如果你对合并历史、处理冲突这些操作还不太熟,还有一个笨办法:克隆远程仓库到临时目录,再把本地文件复制进去。具体命令如下:
git clone git@github.com:你的用户名/仓库名.git /tmp/sync_temp cp -r 本地文件夹/* /tmp/sync_temp/ cd /tmp/sync_temp git add . git commit -m "导入本地文件" git push origin main这个方案的精髓在于:你直接继承了远程仓库的全部历史,复制进来的本地文件只会作为新的修改出现。如果本地文件和远程文件有同名内容,Git 会把它当作“文件的修改”而不是“历史的冲突”,处理起来心理负担小很多。缺点是文件位置变了,如果你的本地文件夹有相对路径依赖、或者有其他程序硬编码了路径,这个方案就不太合适了。但如果只是单纯想“把我电脑上的文件弄到 GitHub 仓库里”,这是最不容易出错的方式。
3. 本地已经是 Git 仓库:如何把远程挂上去并把历史接起来
很多开发者的实际情况是:本地文件夹早就git init了,甚至已经提交了好几个版本,只是当时没有建远程仓库。后来在 GitHub 上补建了一个仓库,现在要把这两边接起来。这个场景的核心就两件事:添加 remote,以及处理两段独立历史的合并。
3.1 添加 remote 的正确姿势与命名习惯
先把远程地址挂到本地仓库上:
git remote add origin git@github.com:你的用户名/仓库名.git git remote -vgit remote -v会列出所有远程地址,确认输出无误后,这个本地仓库就和远程仓库建立了关联。这里解释一下origin:它只是一个约定的名字,不是 Git 的强制要求。你可以起名叫github、upstream,都没问题。只不过全世界的教程都用origin,你用这个约定,后面复制命令时最省心。
如果之前挂过别的地址,用下面的命令改掉:
git remote set-url origin git@github.com:你的用户名/仓库名.git如果你想把原来仓库保留,同时新增一个远程地址,就用git remote add upstream 新地址。一个本地仓库可以同时挂多个远程,这在向别人项目贡献代码的时候很常见:origin指自己 fork 的仓库,upstream指原始仓库。
3.2 main 还是 master:分支名不一致是最大的隐形坑
本地仓库和远程仓库都能正常推送的前提,是两边分支名一致。GitHub 从 2020 年开始,新建仓库的默认分支就叫main,而本地很多老版本 Gitgit init出来的默认分支还叫master。两边分支名不一致时,git push origin main会因为没有同名的远程分支而创建出一个新分支,看起来怪怪的,也容易造成混乱。
先检查一下本地当前分支:
git branch如果输出是* master,直接把分支改名:
git branch -M main-M是强制改名,后面跟新名字。改完之后再执行远程绑定和推送,就不会出现“两个默认分支并存”的尴尬。另外,可以在全局配置里把默认初始分支直接改成 main,一劳永逸:
git config --global init.defaultBranch main这个配置只对你本机新建的仓库生效,已经存在的仓库要手动改名。
3.3 两段独立历史对接的完整示例
假设一个具体场景:A 同学本地有一个仓库,提交了三次,分别加了项目说明、核心代码和测试用例。GitHub 上新建仓库时自动生成了 README.md 和 .gitignore,所以远程那边有两个提交。现在要同步。
第一步,拉取远程内容到本地,这一步不会改动你的工作区,只是把远程提交下载到本地缓存起来:
git fetch origin git log --oneline origin/maingit log --oneline origin/main让你先看看远程有哪些提交,做到心里有数。第二步,执行合并:
git merge origin/main --allow-unrelated-histories这次合并且会把远程的 README 和 .gitignore 拉进你的本地仓库。如果本地没有同名文件冲突,合并会自动成功并生成一个合并提交;如果有冲突,就按上一节介绍的方法处理。第三步,推送:
git push -u origin main这之后,本地仓库和远程仓库就真正同步了,后续只需要遵循“先 pull 再 push”的节奏。
3.4 什么时候可以放心用 force push,什么时候绝对不行
有些教程会教你用git push --force强行覆盖远程仓库,这招确实能解决“历史不相关”的问题,但代价非常大。我的建议是,只有在下面这些条件全部满足时,才考虑强制推送:
- 远程仓库是你一个人用的,没有任何同事或朋友 clone 过;
- 远程仓库里的提交对你没有保留价值,丢了一点不心疼;
- 你非常清楚远程提交被覆盖后,他人如果拉取会直接报错。
即使要强制推送,我也强烈建议用更安全的--force-with-lease而不是--force。前者会在推送前检查远程分支是否还是你上次 fetch 时的状态,如果别人在这期间推了新内容,它会拒绝执行,相当于给你上了一道保险:
git push --force-with-lease origin main在多人协作的共享分支上,永远不要使用强制推送。因为你覆盖掉的可能是同事刚提交的工作,这种事情一旦发生,整个团队的信任感都会崩塌,而且恢复成本极高。
4. 日常同步节奏:先拉后推,冲突别慌
本地与远程成功“第一次握手”之后,真正的考验在日后的日常同步。很多人在第一次同步成功后,就天天下意识先 push 后 pull,结果隔三差五遇到冲突。其实只要把顺序反过来,90% 的麻烦都能避免。
4.1 pull 到底做了什么,rebase 和 merge 选哪个
git pull本质上是一个组合命令:先git fetch把远程提交下载下来,再执行一次合并。合并有两种策略,一种是普通合并(merge),另一种是变基(rebase),它们的结果差异在线条历史上。
普通合并且会生成一个“合并提交”,表示“我把两条分支的脉络在这里汇合了”,历史看起来会有分叉。变基则会把你的本地提交“抽出来”,重新排在远程提交的后面,形成一条线性历史,看起来更清爽。
我个人的选择很简单:个人项目和刚上手的新手,优先用git pull --rebase,因为线性历史在回滚和看提交记录时都非常直观。如果你对 rebase 的机制一知半解,先用普通的git pull也没问题,至少不会把事情弄坏。用下面命令可以设定默认策略,以后直接敲git pull就会走 rebase:
git config --global pull.rebase true4.2 一套不容易出错的日常流程
我给自己定的日常同步流程是五步,每一步都有明确目的:
git status查看当前工作区状态;- 有未提交改动时,先
git commit或git stash; git pull --rebase拉取远程最新内容;git push推送本地提交;- 收工前再看一眼
git status,确认工作区干净。
第二步特别关键。很多人在 pull 的时候被提示本地修改会被覆盖,其实就是跳过了一步。把改动 commit 或 stash 之后再拉取,你就在“保护自己的工作成果”的前提下获取远程最新代码,后续即使有冲突,也只是两个明确版本之间的冲突,而不是“未知远程内容”和“半成品改动”之间的碰撞。
4.3 冲突标记逐行解读
很多人一看到冲突就汗毛倒竖,其实冲突文件里的标记非常有规律:
<<<<<<< HEAD 这是本地分支的代码 ======= 这是远程分支的代码 >>>>>>> origin/main<<<<<<< HEAD到=======之间的内容,是你当前所在分支的版本;=======到>>>>>>> origin/main之间,是远程版本。你要做的事就是把这个文件打开,决定最终保留哪部分,然后把三个标记行全部删掉,保存文件。
如果分不清该留哪边,我的经验是:先看这个文件的用途。配置文件、README、依赖清单这类文件,往往双方各有部分内容需要合并;代码文件则要看具体逻辑,两边改的地方是否互不干扰。最忌讳的是为了省事,整段直接选一边,然后不测试就推送。处理完冲突文件后:
git add 冲突文件 git commit如果冲突发生在 rebase 过程中,不是 commit 而是:
git add 冲突文件 git rebase --continue实在处理不了,想退回拉取前的状态,用git rebase --abort,能让你回到 rebase 之前的状态。
4.4 误操作之后的“后悔药”:reflog 和 revert
万一你在同步过程中手一抖,把改动弄丢了,先别慌。只要那个改动曾经被 commit 过,大概率还能找回来。
如果只是本地执行了git reset把提交弄丢了,用 reflog 找回:
git refloggit reflog记录了 HEAD 指针的所有移动历史,包括你 reset 之前的那个提交。找到你想恢复的提交的哈希值,然后git reset --hard 哈希值就能复活。
如果错误操作已经 push 到了远程,比如你发现远程仓库里有一个不该存在的提交,更推荐用git revert而不是 reset,因为 revert 会生成一个“反向提交”,不会改历史,其他人拉取时不会出问题:
git revert 哈希值这个操作在多人协作的项目中尤其重要。永远不要通过“本地 reset 再 force push”的方式去撤销远程提交,那是把风险转嫁给所有人。
5. 认证配置与权限排错:推不上去先别怀疑仓库
同步操作频繁之后,另一个高频问题就是认证。命令行输出一大段报错,很多人第一反应是“仓库地址是不是写错了”,其实是认证方式压根没配好。GitHub 支持两种认证方式,搞清楚差异能省很多时间。
5.1 SSH 和 HTTPS + Token,怎么选
| 认证方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| SSH | 配置一次后免输密码,稳定 | 每台设备都要生成并配置密钥 | 自己的电脑、长期开发的服务器 |
| HTTPS + Token | 无需管理密钥文件,临时用最方便 | Token 会过期,需要重新生成 | 临时设备、需要快速拉取代码的场景 |
我的建议是:主力开发机用 SSH,一次性临时环境用 HTTPS。原因很简单,SSH 配置一次就能长期免密推送,而 HTTPS 在浏览器登录后虽然也能自动记住凭证,但换设备或者 Token 过期时又得折腾一遍。
5.2 SSH 密钥的生成与测试
在终端执行:
ssh-keygen -t ed25519 -C "你的备注文字"-t ed25519指定密钥类型,这是目前推荐的做法,安全性好且生成快。-C后面可以写一段备注,通常是你的设备名或用途,方便日后在 GitHub 上区分多台机器。一路回车会生成两个文件:私钥~/.ssh/id_ed25519和公钥~/.ssh/id_ed25519.pub,其中私钥千万不要外泄。
然后查看公钥内容:
cat ~/.ssh/id_ed25519.pub把输出的整段内容复制下来,粘贴到 GitHub 个人设置里的 SSH Keys 区域。最后测试是否配置成功:
ssh -T git@github.com如果屏幕上出现类似Hi 你的用户名! You've successfully authenticated的提示,说明密钥已经生效。注意这里测试的主体不是具体的仓库,而是你的账号和 GitHub 之间的认证通道,所以它不依赖任何仓库权限。
5.3 HTTPS 方式下用 Token 代替密码
GitHub 在多年前就已经不再接受账户密码作为 HTTPS 推送的凭证,你需要在 GitHub 设置里创建一个 Personal Access Token(个人访问令牌),生成时勾选repo相关权限。推送时遇到密码输入框,填入的不是你的登录密码,而是这个 Token。
日常使用中,配置一个 credential helper 可以让你不用每次输入:
git config --global credential.helper store但要清楚,store模式会把凭证明文保存在磁盘上,安全敏感的设备我不建议用。Windows 和 macOS 上通常有系统级的凭证管理器,Git 会自动调用,安全性比明文存储好很多。
5.4 常见报错速查表
最后整理一份我在排查中高频遇到的报错对照表,直接对着查就行:
| 报错信息 | 大概率原因 | 排查方向 |
|---|---|---|
Permission denied (publickey) | SSH 密钥没配置或没被识别 | ssh -T git@github.com测试,重新检查公钥是否粘贴正确 |
Authentication failed | HTTPS 方式下用户名或 Token 不对 | 确认账号名和令牌,Token 是否过期 |
Repository not found | 仓库地址错误,或账号无查看权限 | 确认 URL 拼写,检查仓库可见性和是否已加入协作者 |
Updates were rejected/divergent branches | 远程有本地没有的提交 | 先git pull或git fetch再合并,不要直接 push |
failed to push some refs | 同上的另一种表述,远程和本地历史不接 | 检查是否需要--allow-unrelated-histories或先 pull |
fatal: not a git repository | 当前目录不是 Git 仓库 | git init或确认是否 cd 到了正确的目录 |
这张表几乎覆盖了我见过的大部分“同步失败”场景。排查顺序也有讲究:先确认仓库地址和认证,再看历史分叉问题,最后才考虑权限。如果一上来就在“历史分叉”上面纠结,而仓库地址根本写错了,那方向就完全反了。
6. 多设备多仓库下的同步习惯:踩过几次坑才明白的细节
技术命令本身并不复杂,真正让很多人痛苦的,是习惯问题。我在多台电脑之间同步过好几个项目,也亲眼见过别人因为一个偷懒的动作丢掉半天的成果。这些经验值得单独写一节。
6.1 开工收工多花十秒,能避免绝大多数事故
我曾见过一个朋友,每天到公司第一件事就是写代码,写完之后 push,push 报错才开始 pull,pull 完发现冲突牵扯了一大堆文件。十秒钟就能解决的问题,硬是拖成了半小时的麻烦。
我现在的习惯是:打开项目先跑git pull,无论前一天是否已经同步过。接着看一眼git status,确认没有别人留下的未提交改动。收工前同样跑一次git status,如果有未提交内容就 commit 或 stash,然后 push。这套动作熟练之后不到半分钟,但它能保证每次开始工作前,本地都是最新状态;每次结束工作时,远程都有完整备份。这就像出门前检查钥匙和手机,看起来不起眼,丢一次就知道它值多少钱。
6.2 .gitignore 一定要从一开始就写好
很多项目刚开始时文件少,觉得 .gitignore 没必要。等把 node_modules、编译产物、环境变量文件一股脑推上远程之后,再想清理就麻烦得多,因为那些文件已经留在历史里了,即使后面删了,也依然存在于 Git 的提交记录中。
一份最基础的 .gitignore 长这样:
node_modules/ build/ dist/ .env *.log .DS_Store __pycache__/ .idea/ .vscode/node_modules/是 Node.js 项目的依赖目录,dist/和build/是打包产物,.env是环境变量文件,里面常常有密钥和配置信息,推上去等于泄露。.idea/和.vscode/是编辑器的本地配置,不同人的编辑器配置不应该混进仓库。
如果你已经不小心把某些文件推上去了,先用下面命令把它们从 Git 的跟踪中移除,但保留本地文件:
git rm --cached 文件名然后提交推送,这些文件就会从远程仓库中消失。
6.3 提交粒度与提交信息,决定几个月后你认不认得自己的代码
提交粒度和提交信息看似跟“同步”无关,实际上关系很大。同步的本质是提交记录的交换,提交记录越清晰,你处理冲突、回滚单个功能、定位问题就越容易。
我的原则是:一次提交只做一件事。修复一个 Bug、新增一个功能、更新一份文档,各占一个提交。提交信息用动词开头,写明做了什么,比如fix: 修复登录表单超时未提示,而不是更新代码。
为什么这跟同步有关?因为在拉取远程内容时,如果本地和远程同时改动了同一个文件,你能根据提交信息快速判断对方改的是什么。如果所有提交都叫“update”,遇到冲突时你根本不知道哪一行是谁为了什么改的,排查难度直接翻倍。
6.4 我自己的三个真实翻车现场
第一个翻车现场发生在很早的时候。我在 GitHub 上新建仓库时不小心勾选了创建 README,本地仓库也有自己的提交,直接 push 报错。当时不知道有--allow-unrelated-histories这个参数,一着急执行了git reset --hard origin/main,把本地所有提交全部删掉了,只能凭记忆重新提交一遍。那次之后我养成了一个习惯:任何仓库,在 push 之前一定先git fetch origin看一下远程到底有没有提交。
第二个翻车现场是 pull 前没处理本地改动。我在一个文件上改了半天的代码,没提交,直接git pull,被提示本地修改会被覆盖。我当时脑子一热执行了git checkout .,半天的工作瞬间蒸发。正确的做法本来就是git stash,我却用最粗暴的方式处理了。教训是:当 Git 明确提示有风险时,不要尝试绕过提示,先停下来想清楚哪个命令是安全的。
第三个翻车现场比较低级但很典型。换了新电脑,忘了配置 SSH 密钥,push 时报Permission denied (publickey),我第一反应是仓库权限问题,把仓库的协作者设置翻了一圈,最后才发现是本地根本没有密钥。从那以后我排查顺序固定为:先ssh -T git@github.com验证认证,再查仓库权限,最后才看历史分叉问题。
这三个翻车现场都算不上技术难题,但每一次都付出了时间成本。写这些不是想说我踩过的坑有多深,而是想提醒大家,本地文件夹和 GitHub 远程仓库的同步,绝大多数问题不是“不会命令”,而是“没有按顺序操作”和“没有确认当前状态”。只要每次动手前,先把git status和git remote -v的结果看清楚,再决定执行哪一步,你就不会在同步这件事上吃太多亏。