痛定思痛,本篇文章也是为了教会更多跟我一样的新手快速入门git这个神奇的东西,本篇将包括,仓库创建,分支管理,协作拉取,冲突解决,标签发布,代码回滚,尽可能地让你通过本篇来快速入门,话不多说直接开始:
然后我们先说一下前情提要:
你是开发者A,你的同事是开发者B,你们两个需要共同开发一个项目,这个项目需要用到WiFi配置和蓝牙模块
第一阶段:初始化仓库&基础配置
步骤一:我们先创建本地仓库
我们先用mkdir创建了一个esp32-idcard的文件夹,然后在这个文件夹中使用指令git init . 这里的点表示的是当前目录(D:\esp32-idcard)这个指令是用来初始化Git仓库,然后我们继续执行git status 来查看当前状态,根据根据反馈信息我们得到
-
Initialized empty Git repository:已初始化一个空的Git仓库
-
in D:/esp32-idcard/.git/ :Git仓库存储在
D:/esp32-idcard/.git/目录中 -
.git目录:这是Git的核心目录,包含所有版本控制所需的数据(提交历史、分支信息、配置等)
On branch master的意思是当前是在master这个分支上,No commits yet 的意思是还没有任何提交,nothing to commit (create/copy files and use "git add" to track)
这一句的意思是没有需要提交的内容,
-
括号中的提示:
-
create/copy files:创建或复制文件到当前目录
-
use "git add" to track :使用
git add命令来开始跟踪这些文件
-
-
意思:工作目录是空的,还没有任何文件被Git跟踪

步骤二:创建初始文件
接下来我们创建一个main.c文件并填写适当的内容,这里内容实现不做详细介绍,主要就是为了随便填点内容
步骤三:添加远程仓库配置


在这里我们选择在gitee上新建一个仓库作为演示并且复制HTTPS路径

接下来我们输入git remote -v 这条命令来查看远程仓库配置,我们可以看到啥也没有,然后我们使用git remote add origin https://gitee.com/xiaofei05/esp32-idcard.git 这条命令来添加我们的远程仓库,这里的https地址需要换成你自己的,最后再次输入git remote -v指令,发现我们以及添加成功。其中有几个注意的点是
-
git remote add:添加一个新的远程仓库
-
origin :远程仓库的别名(约定俗成使用
origin作为主远程仓库的名称)
我们还发现这里好像显示了两个仓库,一个是括号fetch结尾,另一个是以括号push结尾,这两个区别是
-
(fetch):获取(拉取)操作的URL
-
意思 :当执行
git fetch origin或git pull origin时,会从这个地址拉取代码 -
(push):推送操作的URL
-
意思 :当执行
git push origin时,会向这个地址推送代码


拉取和推送我们后面会讲到,这里先仅做了解,我们可以看到,在我们的文件夹中已经出现了main.c这个程序,但是在仓库中还没有,因为这个只是已经被创建存在了,还没有被git跟踪添加
本地文件系统 Git工作区 Git暂存区 Git本地仓库 Git远程仓库
main.c ──未跟踪──> 空 ────> 空 ────> 空
(已存在) (未add) (未commit) (未push)
主要的流程是先添加到暂存区,在提交到本地仓库,最后再推到远程仓库

这个时候我们再使用git status可以看到我们在主分支master,还没有提交记录,Untracked files表示未跟踪的文件,他会把当前目录没有跟踪的文件全部列在下面,就比如这个main.c文件
-
nothing added to commit :没有添加到暂存区准备提交的内容
-
but untracked files present:但是存在未跟踪的文件
-
(use "git add" to track) :使用
git add来开始跟踪这些文件
-
-
整体意思:暂存区是空的,但工作目录中有Git尚未跟踪的文件
步骤四:首次提交和推送

我们使用指令git add . 来将当前目录下所有文件和子目录添加到暂存区,就是为了开始跟踪所有未跟踪的文件,并暂存所有修改。Changes to be committed:表示有改变并将要提交的文件,也给你用绿色列出来了,就是我们刚添加到暂存区的main.c文件。然后我们调用git commit -m "feat: initial project setup for ESP32 ID card reader"这句命令来将暂存区的文件提交到本地仓库
-
git commit:将暂存区的文件提交到本地仓库
-
-m:指定提交消息
-
引号中的内容:提交消息,使用了约定式提交格式
-
feat::表示这是一个新功能 -
initial project setup for ESP32 ID card reader:具体描述
-
这里还涉及到了约定是提交(Conventional Commits),除了feat表示新功能之外还有很多常用的,比如说fix表示修复bug,docs表示文档更新,style表示代码样式调整(不影响功能),refactor表示代码重工(不添加功能也不修复bug),perf表示性能优化,revert表示撤销之前的提交,merge表示合并分支,还有很多这里就不过多举例了,请自行查询
然后还显示了
On branch master
nothing to commit, working tree clean
这说明工作目录跟暂存区都是干净的,所有更改都已经提交了,然后我们调用git push -uorigin main指令尝试推送到main分支,然后报错了,这是因为gitee这个仓库默认生成的是master分支,所以我们应该是推送到master分支上才对,现在我们再来看一下远程仓库就会发现多了一个main.c文件
第二阶段:创建dev分支&开发功能
步骤五:创建并切换到dev分支

我们使用指令git branch可以查看当前本地所有分支发现只有一个master分支并且前面有个*(星号),星号表示的是当前所在的分支,然后我们调用git checkout -b dev指令创建了一个新的分支dev,其中 -b 是--branch的简写,表示创建了新分支,这一句指令相当于执行了 git branch dev # 创建分支 git checkout dev # 切换到该分支 这两个指令
步骤六:在dev分支开发(wifi功能)

这里请自行创建一个wifi.c的文件吧,这里内容就随便写写了然后我们可以看到有引得准备提交的文件wifi.c,然后我们进行提交并使用 -m 来叙述,然后用git status来查看发现已经提交完了并且当前是在dev分支上
步骤七:推送dev分支到远程仓库

ok我们成功的把dev分支上的wifi.c代码也推到了远程仓库上,下面是对指令git push -u origin dev的拆分解析
-
git push:将本地提交推送到远程仓库
-
-u :
--set-upstream的简写,建立跟踪关系 -
origin:远程仓库别名
-
dev:要推送的本地分支名
-
整体意思:将本地的dev分支推送到origin远程仓库,并建立跟踪关系



然后根据这三张照片我们可以看到,我们现在是有两个分支的,因为我们现在是在dev分支开发,远程仓库的main分支是独立的,只有git merge dev 合并之后再git push origin main,远成main才会更新,这是git分支隔离的设计核心------dev的代码不会自动污染main,确保主分支始终稳定,后面会讲到什么是合并如何合并,这里有个大体印象就行。
第三阶段:模拟同事修改
这种情况总是无法避免的,所以我们要学会如何解决,如果修改了同一份文件就需要merge,没有改的文件不影响,冲突就是远程仓库文件版本与你本地的文件版本不同了,就意味着两个人改了同一个文件,接下来我们模拟一下
步骤八:同时克隆仓库&修改main

这里模拟同事使用git clone克隆远程仓库到本地,Already on 'master'表示已经在master分支上,Your branch is up to date with 'origin/master'.这句话的意思是你的分支与"origin/master"是最新的,本地master分支与远成origin/master分支完全同步,也就是说不需要拉取更新,代码是最新的。


这里输入的时候出了点小错误,毕竟我也是菜鸡,请原谅,更改之后我们发现两次的main.c内容已经被更改了,也就是说我们在dev分支修改了main.c的loop()函数,但是现在我们的同事在measter分支也修改了这个loop()函数,这也就出现了我们一开始说的冲突问题,当我们合并时,git就会检测到重读,需要我们手动解决

Changes not staged for commit:表示已修改但未添加到暂存区的文件,modified: main.c这个表示已修改的文件,Untracked files:这个表示未跟踪的文件

当我们提交代码并推送之后我们发现,我们的主分支master多了个ble.c文件和main.c文件被修改了

第四阶段:你拉取更新&检测变更
步骤九:定期fetch检查远成变更

我们使用指令git fetch origin发现
-
8ed2286..a993cac:从提交8ed2286到a993cacmaster -> origin/master:更新了origin/master分支的指向
-
含义:远程master分支现在指向a993cac提交

说明远程仓库已经变更了,然后我们调用git --no-pager log master..origin/master --oneline
这个指令的意义是查看在origin/master中有,但在本地master中没有的提交
-
a993cac:提交的哈希值(前7位)
-
(origin/master):这个提交现在在origin/master分支上
-
feat: add BLE module and update main loop:提交信息
现在说明同事推送了新代码,你必须同步后在合并你的dev分支
步骤十:尝试合并前,先同步你的main分支

我们先用pull拉取代码,显示两个文件被更改,create mode 100644 ble.c表示创建了ble.c文件,然后使用git status确认同步完成,Your branch is up to date with 'origin/master'.表示本地与远程完全相同

然后调用git --no-pager log --oneline -3 master查看提交历史
-
显示两个提交:
-
a993cac:同事的BLE提交(当前HEAD) -
8ed2286:你的初始提交
-
-
标签:
-
HEAD -> master:当前HEAD指向本地master分支 -
origin/master:也与远程master分支相同
-
步骤十一:给当前稳定状态打标签

我们调用git tag -a v1.0 -m "Stable before dev branch merge"这句指令
-
git tag:标签管理命令 -
-a:--annotated的简写,创建带注解的标签 -
v1.0:标签名称(遵循语义化版本号) -
-m:指定标签消息 -
"Stable before dev branch merge":标签的描述信息
你的标签消息说明:"Stable before dev branch merge"
-
意义:在合并dev分支到master之前创建一个稳定点
-
作用:如果合并出问题,可以快速回退到这个版本
标签与分支的区别:
| 特性 | 标签 (Tag) | 分支 (Branch) |
|---|---|---|
| 移动性 | 固定,不移动 | 随着提交移动 |
| 用途 | 标记特定版本 | 开发线 |
| 示例 | v1.0, v2.0 | master, dev |
| 可修改 | 否(通常) | 是 |
| 推送 | 需要显式推送 | 随提交推送 |
然后调用git push origin v1.0推送标签到远成仓库

然后我们就可以看到在这个仓库中多了个标签,我们也可以下载这个版本
步骤十二:在dev分支开发(wifi功能)
我们现在捋一下当前是什么状态,我们一开始是创建了个master分支,里面只有一个main.c文件,并且内容是这样的

然后我们创建了dev分支,打算在这个分支里面开发wifi的功能,并且已经创建了wifi.c且推送到了远程仓库,注意这时候我们dev分支下的main.c文件是跟master分支下的main.c文件是一样的,之后呢我们的同事gitc lone了这个仓库的代码,并且呢我们的同事创建了ble.c文件和修改了master分支的main.c里的内容,并且推送到了远成仓库上面

这个时候呢我们使用fetch发现远程仓库被同事修改了,也就造成了冲突,所以我们就需要拉取然后合并

通过这张图片我们可以很清晰很直观的感受一下,左边是同事当前的工作项目,右边是我们拉取之后的工作项目,我们发现拉取之后的master分支跟远程仓库里的代码是一样的。
ok现在基本捋清楚了,没懂的再仔细体会一下,然后我们开始继续在dev分支开发wifi功能:


ok我们现在对dev分支下的main.c进行了修改,当我们push到远程仓库之后然后进行合并,我们的dev分支下的main和master下的main产生冲突因为他修改了同一个文件的代码,因为你现在修改之后的main是

但你master分支里的main是

所以说在你合并的时候第三行一定会有冲突
刚刚我又犯了个小错误就是我们现在在合并前应该需要切换到master分支再pull一次,以保证这是最新的代码,结果我不小心没切换master分支就直接pull拉取了,我们可以调用git reset --hard HEAD~1彻底丢弃最近一次提交,包括它的所有文件修改,回到上一个提交的状态,(如果你用的是--soft的话他不会丢弃刚刚的文件修改),因为我们刚刚已经推送过了,所以这里我们再git pull origin dev从仓库拉一下我们将要合并前的样子

然后我们发现再合并的时候出现了冲突
Auto-merging main.c
- Git尝试自动合并main.c文件
CONFLICT (content): Merge conflict in main.c
-
冲突:main.c文件内容冲突
-
类型:内容冲突(文件内容不一致)
Automatic merge failed; fix conflicts and then commit the result.
-
自动合并失败
-
指示:需要手动解决冲突,然后提交结果
按理来说你现在再调用cat main.c来查看应该是会有类似这种的冲突标记的

但是我这个在powershell中,Set-Content 默认使用 UTF-16 LE(带 BOM)编码 写入文件这种编码在文件开头会插入不可见的字节(BOM) Git 会误判为"二进制文件",拒绝在二进制文件中插入冲突标记,你也可以自己重新复制粘贴到记事本改一下编码另存为UTF-8编码的程序,然后重新push到远程仓库,这里由于发生的冲突比较简单,我就直接手动编辑了

我直接手动编辑master分支下的main.c程序,这里已经编辑好了如上图所示。

然后我们调用git add main.c 标记冲突已解决,告诉git我已经处理好了main.c的冲突,实际上就是将main.c从"未合并状态",移到"已暂存准备提交"。All conflicts fixed but you are still merging.表示你已经解决了冲突,但还需要提交来完成合并。[master 6e07750] Merge branch 'dev'表示在master分支创建了新的合并提交6e07750
我们也可以通过调用git log --oneline --graph --all来查看一下完整的提交历史

然后确认无误之后你就可以调用git push origin master来推送最终的结果了



然后你就可以打开你的gitee仓库看看是不是都有以上图片的东西
最后我们再来稍微学一下代码回滚

我们第一步先调用了git --no-pager log --oneline -2来查看当前状态,这一句的作用是快速查看最近2个提交
6e07750 (HEAD -> master, origin/master) Merge branch 'dev' ← 当前 HEAD
a2d8d7b (origin/dev, dev) fix: update main loop...
意义是确认你处于"合并完成后的状态",这是我们进行代码回滚实验的起点
然后我们调用git reset HEAD~1来执行回滚,也就是撤销最近一次提交,从6e07750回退放到了a993cac(即v1.0)
Unstaged changes after reset:
M main.c
说明文件修改还在,只是"提交"被撤销了
然后我们调用git status查看状态
- 输出解读 :
Your branch is behind 'origin/master' by 3 commits
→ 因为本地回退了,但远程origin/master仍在6e07750modified: main.c
→ 合并带来的代码修改还在工作区untracked: wifi.c
→ Git 认为这是新文件(因为回退后它不再属于任何提交)- 这说明:
git reset只改变提交历史,不删除工作区文件。
然后我们调用了git --no-pager log --oneline -1输出a993cac (tag: v1.0)这代表我们确实回到了打标签的状态(合并前)
然后我们调用git reset --hard 6e07750强制回到指定提交(6e07750),他的作用是我们又恢复到合并提交,工作区和暂存区也会完全重置为6e07750的状态,git status 会显示 "nothing to commit"。所以通过这个我们可以学到只要记住 commit ID,就能随时回到那个历史状态