Git管理经验
Git 远程推送与同步指南
首次推送
当你第一次将本地代码推送到远程仓库时,使用以下命令:
1、初始化文件夹,添加git配置
1 | git init |
2、为git添加一个名为origin的远程地址(仓库)
1 | git remote add origin https://github.com/你的用户名/仓库名.git |
3、将本地缓存区的代码推送到origin这个远程仓库的main分支
1 | git push -u origin main |
-u等同于 --set-upstream,它将地址写入项目文件夹的.git/config,它告诉 Git,“以后如果不指定目标,默认就推送到 origin 的 main 分支”。
这样以后的推送只需要输入 git push 即可,无需再敲长命令。
首次拉取
1. 最常用的方式:克隆 (Clone)
这是将远程仓库“搬家”到你本地的最快方式。
1 | git clone https://github.com/你的用户名/仓库名.git |
git clone 是一个“打包套餐”,它一次性帮你做完了以下三件事:
git init:创建了一个新的文件夹,并初始化了 Git。git remote add origin ...:自动把远程地址关联好了,并起名为origin。git pull:把远程所有代码下载下来,并自动切换到默认分支(通常是 main)。
2. 特殊情况:本地已有文件夹,想拉取指定地址
如果你本地已经建立了一个文件夹(甚至已经初始化了),现在想把它和某个特定的远程 URL 关联并拉取代码:
第一步:关联远程地址
1 | git remote add origin https://github.com/你的用户名/仓库名.git |
- 注意:如果提示
error: remote origin already exists,说明你之前关联过别的地址。可以使用git remote set-url origin 新地址来修改。
第二步:拉取代码
关联后,将代码拉取到本地:
1 | git pull origin main |
- 注意:如果本地文件夹里已经有文件,且和远程仓库不一致,可能会报错。此时请参考下文提到的 “报错:Refused / Fetch First” 章节,使用
--allow-unrelated-histories参数解决。
报错:Refused / Fetch First
报错现象
当你执行 push 时,如果出现以下错误:
! [rejected] main -> main (fetch first)
hint: Updates were rejected because the remote contains work that you do not have locally.
原因
远程仓库比你本地多了一些文件(通常是因为你在 GitHub 建仓库时勾选了创建 README.md 或 .gitignore),即远程仓库上的代码并非本地代码的历史提交。Git 为了防止你覆盖掉远程的内容,阻止了推送。
解决方案:合并(Merge)
我们需要先把远程的东西拿下来(Pull),合并到本地,然后再推上去。
步骤一:确保本地已提交
在拉取之前,必须保证本地所有修改都已保存到本地仓库:
1 | git add . |
步骤二:允许合并不相关的历史
先来解释一下git的历史提交机制:
什么是“历史”?
在 Git 眼里,你的项目不是一堆文件,而是一串提交链。
每一个Commit都包含两样东西:
快照:这一刻所有文件的样子。
父节点指针:指向它的前一个提交(我是从哪儿来的)。
1 | A <-- B <-- C |
C 知道它的父节点是 B。
B 知道它的父节点是 A。
这就是“历史”。
什么是“共同历史”?(分叉路口)
假设你和你的同事小王,都从 C 这个节点开始工作:
你写了两个功能,提交了 D 和 E。
小王改了别的地方,提交了 F 和 G。
现在的结构图是这样的:1
2
3
4
5
6
7 (你的分支)
D <--- E
/
A <-- B <-- C <--- 这就是【共同祖先 / 共同历史】
\
F <--- G
(小王的分支)
在这个图里,节点 C 就是你们的共同历史。
它是你们分道扬镳之前的最后一个“同步点”。
Git 极其依赖这个点,因为它是判断“谁改了什么”的基准(Base)。
Git 的合并机制:三方合并
当你执行 git merge(或者 git pull)把小王的代码合并到你这里时,Git 实际上是在做一个三方对比。
它会找来三个版本的代码:
你的最新版 (E)
小王的最新版 (G)
共同祖先 (C) —— 这最关键!
Git 会查看:“相对于祖先 C,你们俩分别改了什么?”
场景 1:C某个部分只有一人更改
祖先 C:
title = "Hello"你的 E:
title = "Hello"(没改)小王的 G:
title = "Hello World"(改了)
Git 推理:既然你没动,小王改了,那结果肯定是用小王的。 👉 结果:title = "Hello World"
场景 2:两个人都改了同一行(冲突 Conflict
祖先 C:
color = "red"你的 E:
color = "blue"小王的 G:
color = "green"
Git产生疑问:
“祖先是红色。你把它改成了蓝,他把它改成了绿。相对于祖先C,你们都动了这一行。我不知道听谁的。”
👉 结果:合并冲突(Merge Conflict)。Git 停下来,让你手动去选蓝还是绿。
回到一开始的问题:如果没有共同历史,怎么办呢?
输入以下指令:1
git pull origin main --allow-unrelated-histories
--allow-unrelated-histories 做了什么? 它告诉 Git我们需要合并不相干的历史。
我们可以从三个层面来看看发生那一瞬间以及之后会发生什么:
文件层面:简单的“物理堆叠”
既然没有共同祖先可以用来对比差异,Git 就会采取最笨也最直接的办法:把两边的文件都倒进同一个文件夹里。
场景 A(平安无事):
你本地有:
main.cpp,project.pro远程有:
README.md,.gitignore结果:你的文件夹里现在有这 4 个文件。大家和平共处。
场景 B(发生撞车):
你本地有:
README.md(内容:这是我的代码)远程也有:
README.md(内容:Github 自动生成)结果:报冲突(Conflict)。
因为没有祖先,Git 无法判断是“谁改了谁”,它只知道都有这个文件且内容不一样。它会把文件标记为冲突状态,强迫你手动打开文件,决定保留哪一段文字。
历史层面:强行认亲
这是最有趣的部分。执行这个命令后,会生成一个新的 “合并提交”(Merge Commit)。
这个提交非常特殊,它有两个父节点,而且这两个父节点来自两个原本不相干的世界。
图示如下:
1 | (本地的历史) |
在那一瞬间:节点
M诞生了。它就像一个“混血儿”,左手拉着你本地的爸爸C,右手拉着远程的爸爸Y。从此以后:因为
M连接了这两条线,Git 就会认为这两条线归一了。下次你再合并,Git 就能顺着线找到M作为共同祖先。
步骤三:再次推送
合并成功后,本地就拥有了远程的文件,此时再推送即可成功:
1 | git push -u origin main |
工具技巧:如何退出 Vim 编辑器
在执行 git pull 合并时,Git 经常会自动打开 Vim 编辑器让你确认“合并日志(Merge Message)”。对于新手来说,这是一个常见的“卡住”点。
操作口诀
当你看到满屏波浪号 ~ 且无法用鼠标点击时:
按
Esc:确保退出输入模式。输入
:wq::(冒号,进入命令模式)w(write,写入保存)q(quit,退出)
按
Enter:执行。
Git 反馈
当显示以下信息时,代表操作成功:
| 关键词 | 含义 |
|---|---|
| Enumerating objects | Git 正在清点要上传的文件数量。 |
| Writing objects 100% | 文件上传传输完成。 |
| main -> main | 本地 main 分支已更新到远程 main 分支。 |
| branch ‘main’ set up to track | 关联成功!以后可以直接用 git push 和 git pull。 |
💡 给开发者的建议
先 Pull 后 Push:养成好习惯,每次准备上传代码前,先运行
git pull拉取队友或远程的最新修改,解决冲突后再git push。慎用 -f:
git push -f是强制覆盖,除非你非常确定要删除远程的所有历史,否则在团队开发中严禁使用。
后续提交完整流程
检查状态
养成好习惯,先看一眼哪些文件变红了(修改了但没暂存)。
1 | git status |
添加文件入缓存区
把所有修改过的文件放入缓存区。
1 | git add . |
(注:. 代表当前目录下的所有文件。如果你只想提交特定文件,把 . 换成文件名)
提交保存
把暂存区的文件生成一个新的版本记录。引号里写清楚你干了什么。
1 | git commit -m "这里写你的修改备注,例如:修复了登录按钮的bug" |
这里相当于提交了一份新版本的代码到本地
拉取更新
在上传到远程仓库之前,必须先检查远程有没有别人提交的新代码。
这一步能避免 90% 的推送报错。1
git pull
(如果没有更新,它会提示 Already up to date,这很好;如果有更新,它会自动合并)
上传到远程仓库
把你的新版本上传到 GitHub。1
git push





