在以往的学习中,我没有很系统的了解过git,我只知道这是一个在多用户协作编程时,用来维护代码版本的方式;使用起来也只是add,commit,push这几个操作(因为现在的学习阶段通常没有协作编程),在windows下还可以使用一些git工具比如TortoiseGit,这个就是俗称的小乌龟(没听过也不要紧),这个小乌龟可以在windows上用图形化界面的方式进行git(当然前提是要先下载了git);而在linux下我们通常是直接使用命令行进行git;但这些远远不够,我们只有了解了git究竟是什么样的,了解了其原理,熟悉其操作,才可以更好的使用这个强大的工具;
1.git作用
在工作与学习中,我们经常需要进行文档的编写,而一份好的文档是需要不断进行修改的,但如果直接在原文档上修改,那么我们每次修改时都会将原有的文档改变,此时就会面临着如果修改的效果不好想回退修改为原来版本这样是无法进行的;如果想做到对版本的控制(回退,修改),那么就需要我们进行一些操作(记录修改与修改的版本等),而版本控制器就是这样一个操作的工具,git就是这样一个版本控制器;

注:git可以控制版本的是电脑上任何形式的文档,而图片/视频/音频这些二进制文件是无法控制版本的,也就是git无法知道二进制文件修改了什么地方,只知道这个二进制文件的大小是否改变(例如10KB->20KB)
2.git下载
先想要使用git,肯定是需要下载git;
centos下载:
sudo yum install -y git
ubuntu下载:sudo apt update
sudo apt upgrade
sudo apt install -y git
windows下载:
小拓展:
在使用apt与yum的时其实还有这样一个疑惑,为什么ubuntu要进行多两步骤update与upgrade?
为什么 Ubuntu 需要
update和upgrade?在 Ubuntu 中,使用
apt系统时,包管理和更新过程分为几个步骤:
apt update:它用于更新本地的包索引。因为每次你执行安装或升级时,系统需要知道哪些软件包是最新的,而这个信息保存在本地的包索引中。所以在进行安装或升级前,需要先运行apt update来刷新这个索引。
apt upgrade:它用于将已经安装的软件包升级到最新版本。即使你已经更新了包索引,你还需要运行这个命令来真正地升级系统上的软件包。这些操作的分开是为了让用户能够更灵活地管理更新。比如,有时你可能只想更新包的索引,但不马上执行升级。或者,你只想升级一部分特定的包,而不需要一整套更新。
为什么 CentOS 不需要
update和upgrade?CentOS 使用的是 YUM (或在新版中是 DNF )作为包管理工具,YUM/DNF 的行为和
apt有些不同:
自动同步 :当你使用
yum或dnf执行安装、升级时,它会自动检查源中的包信息并在后台执行类似apt update的更新操作。例如,执行yum install <package_name>时,YUM 会先自动检查源信息并更新索引,然后再执行安装操作。
yum update和yum upgrade:在 CentOS 中,这两个命令通常合并为一个yum update,执行时会自动更新包索引并升级所有系统中的包,所以你不需要单独运行两个命令。因此,CentOS 的包管理工具自动化了
update和upgrade的步骤,而 Ubuntu 则需要显式地运行这两个命令,以便用户能够更细致地控制更新过程。总结
- Ubuntu 使用
apt需要明确分开update(更新包索引)和upgrade(升级包),这给用户更好的控制。- CentOS 使用
yum或dnf,它们在执行安装或升级时会自动进行包索引更新,所以通常不需要显式地运行类似update的命令。这是两种不同的 Linux 发行版在包管理系统上的设计选择,Ubuntu 更注重细分操作,而 CentOS 更注重自动化。
回归主题,下载好了git后我们可以查看一下我们是否下载好了git:
查看版本:
git --version
下面这样就是下载好了:
windows(使用win+r打开powershell输入):
ubuntu(centos类似):
随着版本不断更新,显示的版本是不同的
3.git本地仓库基本操作
下载好git后,我们想要使用git管理我们的文档,这个时候就需要给git一个独立的空间让git存放它他要管理的文档,这个空间就叫做git仓库 ,下面我们看看git关于本地仓库的操作;
接下来的操作windows与linux都是差不多的:
3.1 创建本地仓库
(这里是在单纯模拟本地.git文件创建,真正现实情况,我们都是直接从远程仓库克隆的)
创建仓库:
mkdir gitfile
git init
现象:可以看到成功创建了一个隐藏文件.git
3.2 配置本地仓库
为了防止之后进行git时出现一些错误,先对git进行配置是必要的;
配置user与email:
git config user.name "你的username"
git config user.email "你的email"
查看配置是否成功:
git config -l
现象:
如果配置错误,也可以删掉刚刚的配置
删除配置:
git config --unset user.name "你的username"
git config --unset user.email "你的email"
此外还有全局配置,就是加上一个--global选项,将此配置项生效与当前机器的所有git仓库;
全局配置:
git config --global user.name "你的username"
git config --global user.email "你的email"
删除全局配置:
git config --unset --global user.name "你的username"
git config --unset --global user.email "你的email"
3.3 本地仓库结构
首先我们来看看git本地仓库中的内容:

本地仓库是这样划分的:

对应关系:

接下来我们添加一个readme文档进行来加深对这个结构的理解;
3.4 添加文件到本地仓库
添加操作:
git add . (添加当前目录下所有文件)
git add readme.txt (添加文件名文件)
git commit -m "日志信息" (将暂存区内容提交到master分支中)
完成了上面的操作后,我们的文档就真正的被git管理起来了;

我们还可以查看git日志:
git log (查看日志信息)
git log --pretty=oneline (好看的查看一行)
git log --pretty=oneline --abbrev-commit (好看的查看一行并简化commit_id)

3.5 查看本地仓库内容
我们成功添加好文档之后,我们结合上面的本地仓库结构,再来查看一下这个本地仓库中的内容:

我们首先通过cat查看到.git中的HEAD文件,发现这个HEAD文件中存储的是一条指向.git/refs/heads/master的路径,我们继续使用cat查看这个路径的文件,可以看到输出了一个commitID(通过下面的cat-file输出可知这是commitID)

我们再使用一条git中的命令:
查看git对象内容的命令:
git cat-file -p (objectID)
我们可以看到上面是输出了commit对象中的内容的,可能上面的内容有些难以理解,我们可以看看下面对git对象的解释:
Git 中的基本对象:
Git 使用几种不同的对象来管理版本控制的数据,主要包括以下几种:
- blob :表示一个文件的内容。每个文件的内容都有一个唯一的哈希值,Git 将文件内容存储为
blob对象。- tree :表示目录的结构。它包含指向文件(
blob)和子目录(tree)的引用,并且它们的哈希值会记录在树对象中。tree对象本质上是一个目录的快照。- commit :表示一次提交,包含了提交信息(如作者、日期和提交信息)、父提交以及一个指向
tree对象的引用(表示该提交的文件目录结构)。
所以说,我们的HEAD指向的是最新的一次提交commitID,通过这个commitID我们可以找到提交时的目录结构,还可以通过打印目录结构,找到这次提交中的内容:

3.6 修改工作区内容
对于修改工作区,无论是进行新增或者是删除又或者是修改文档内容,这些都算是修改工作区;在我们修改了工作区后,我们可以通过这些命名来查看仓库的状态以及修改了哪些些内容:
查看工作区状态:
git status
查看修改的内容:
git diff 文件名
现象: 
小拓展:
如果想要修改文件的名字,需要使用git自带的命名,不能直接使用mv来修改,直接使用mv命名修改git会认为是删除了原文件创建了新文件;
修改文件名:
git mv 原文件 新文件名

3.7 版本回退
当我们修改好了我们的文档后,发现修改的内容我们不喜欢,我们还是喜欢之前的版本,那么这个时候就需要进行版本回退(也不一定是回退也可以说是切换);
版本回退命令:
git reset --选项
选项:
--soft (只回退版本库)
--mixed (回退版本库和暂存区)
--hard (版本库暂存区工作区全部回退)
下面我们来演示一下回退操作:

看了这个基本的版本回退操作后,我们应该可以知道只要拿到commitID,就可以进行不同版本的切换(一般一次commit就是一次版本的改变);我们可以通过下面的命令获取所有的commitID:
获取历史变化版本:
git reflog
现象:
输出解释:
在
git reflog的输出中,像9c3b432 (HEAD -> master)这样的部分表示当前HEAD引用指向的分支以及HEAD所在的位置。具体来说:解释:
HEAD -> master:
HEAD是一个指针,表示当前检出的提交位置。通常HEAD会指向当前所在的分支的最新提交。(HEAD -> master)表示HEAD当前指向的分支是master,也就是说,HEAD现在指向master分支的最新提交。
HEAD@{n}:
HEAD@{n}是 Git 的一种机制,表示HEAD在过去的某个时刻的状态。n是一个数字,表示HEAD所在的历史位置。例如,HEAD@{0}表示当前HEAD的位置,HEAD@{1}表示上一位置,HEAD@{2}表示上上一个位置,以此类推。- 这些记录通常会出现在
git reflog输出中,用于帮助你查看分支和HEAD的历史记录,尤其是在你执行git reset、git commit等操作时。输出中的示例:
9c3b432 (HEAD -> master) HEAD@{0}: reset: moving to 9c3b43283ef71dc43b81b2adc54f4a6b5c6e2b0f:
9c3b432是当前提交的哈希值。(HEAD -> master)表示当前HEAD正指向master分支。HEAD@{0}表示这是当前HEAD的状态。reset: moving to 9c3b43283ef71dc43b81b2adc54f4a6b5c6e2b0f表示执行了一个git reset操作,HEAD被移动到了这个提交。
99bdadf HEAD@{1}: reset: moving to 99bdadf077e12d3e8149c65a95f8265959874fcd:
99bdadf是先前一个提交的哈希值。HEAD@{1}表示这是HEAD的前一个状态。总结:
(HEAD -> master)表示HEAD当前指向的分支是master。HEAD@{n}表示HEAD在过去某个时间点的状态,可以用来追踪HEAD的历史位置。
git总是有后悔药可以吃,commitID就是我们的后悔药,当我们的commitID都无法准确获取时,这个时候我们最后的后悔药也会丢失造成版本无法切换的情况;
3.8 撤销修改
前面我们说到了我们修改了代码后发现代码不够好想要回退版本为原先的版本,我们可以使用reset来进行操作;撤销修改当然也可以继续用前面讲的reset来执行,只不过是回退的版本是当前版本而不是其他版本这一变化而已,此外当我们只修改了工作区内容时可以使用下面这个命名回退到版本库内容;
撤销修改操作:
git checkout -- 文件名
仅修改工作区情况:

但是如果我们进行了add甚至是commit操作时这个时候就需要reset来配合进行撤销了;

当我们进行了git add发现我们的修改不够好想要回到之前版本时我们可以直接使用git reset --hard HEAD 全部回退到当前版本,也可以使用git reset --mixed HEAD将版本库与暂存区回退到当前版本后再使用git checkout -- 删除修改;总之我们需要灵活进行变通的使用reset;
暂存区,工作区被修改情况:

版本库,暂存区,工作区 全部被修改情况 :

3.9 删除操作
在git中,删除一个文件其实和修改是一样的,我们需要做的事情是和 3.6修改工作区中内容 是一样的,我们删除某个文件后依然需要将当前目录进行git add . 操作,之后我们再进行git commit 将删除提交到版本库;
直接使用操作系统的rm命令:

使用git的rm命令:
git rm 路径

git rm比操作系统的rm多做了一个git add操作,git rm后只需要进行git commit操作即可完成删除;
4.git分支管理
什么是git分支,我们可以看作是两条不同的路径,你可以在两条不同的路径上编码,两边在编码时互不干扰,就像是平行时空一样:

但在我们的编程中,一般这样创建的分支可以是用来调试代码的测试用的分支,这些分支在将代码调试完成修改完BUG后会将分支结合回主分支;
4.1 创建/查看/合并分支
接下来我们来看看最简单的创建分支修改分支内容到合并分支的过程:
过程参照图:
操作:
查看分支:
git branch
创建分支:
git branch [分支名]
切换分支:
git checkout [分支名]
合并分支:
git merge [分支名] (将分支添加到当前分支)
操作现象:

我们可以看到两个分支编码时确实是不会互相影响的,那如果我们合并两个分支呢?会发生什么情况?
master分支不变,仅修改test分支情况:


这里的情况是最简单的情况,是master分支没有发生改变,直接将master分支的最新提交指向改为指向test分支最新提交即可,但真实的世界情况是非常复杂的我们继续往下看;这个简单过程还可以参照git链接中的图理解:图片/git分支(初).png · future/my_road - Gitee.com ;
4.2 删除分支
删除分支:
git branch -d [分支名] (不能在要删除的分支上删除自己)

因为这样的删除分支的操作非常简单,在我们合并了分支之后对于不需要的分支,我们可以直接删除,这样的删除方式是被鼓励的,可以减少分支太多而混乱的情况;
但也有些特殊情况,比如我们创建的test分支进行了提交更新到了test的版本库,而我们又发现这个test分支写的不够好,想要删除这个test分支时,我们这个时候使用-d是无法删除的,我们需要使用-D选项来删除;
强制删除分支:
git branch -D [分支名]
4.3 合并冲突
有时候创建再切换分支有点麻烦,我们可以使用下面的命令在创建分支时直接切换:
创建并切换分支:
git checkout -b [分支名]
接下来我们看一个复杂一些的合并情况,当两个分支都发生了修改时,对这两个分支进行合并出现的情况:


我们我们可以看到发生了合并冲突,我们人为的进行了修改合并; 所以如果遇到这样的情况我们进行merge后需要人为的对代码进行修改,查看其中冲突的代码部分,修改完成后还要记得再进行一次提交;
小拓展:
git log还可以使用这个格式更明显的查看到分支合并的情况:
查看分支合并情况:
git log --graph --pretty=oneline --abbrev-commit (选项可以增删改,可以自己测试一下)

4.4 不使用fast-forward模式合并
在前面的合并的例子中有简单的直接合并的例子,也有复杂的出现合并冲突的例子;在这其中简单的合并的例子中我们说到的fast-forward,这个模式在删除了分支后,我们会无法分辨提交是merge合并还是自己的提交,所以我们需要带--no-ff选项,不使用fast-forward模式合并;
不使用fast-forward模式合并:
git merge --no-ff -m "提交日志" [合并分支]


4.5 分支作用
通过前面的讲解,我们知道了分支是如何操作了之后;接下来我们说一说分支的作用:在一款服务软件或者网站为用户提供服务时,用户使用的一定是稳定的程序,而这些稳定的程序的代码都是在我们的主分支上的,我们通过不断创建分支,在分支上对代码进行增删改,不断更新程序的功能,当一个功能比较完善且稳定时就可以合并到主分支中,这就是分支的作用;

4.6 解决BUG场景
在4.5 分支作用处,我们知晓了程序通过git来更新的过程,而程序更新也包含了解决BUG,而在这个场景下有一些需要注意的地方:
场景:
在master主分支创建了update分支对代码进行更新,而在update分支进行更新时,发现master分支上有BUG,这个时候需要切换回master分支修改BUG,再回到update分支上更新代码,最后合并代码回master分支;这个场景会出现一些问题需要我们注意:
场景的图片大概是下面这样:

当我们遇到这个场景时,首先我们需要先切换分支到update,将我们在update分支中完成的代码部分存储到stash区中,如果不存储到stash区中由于工作区中的代码没有提交到版本库中,所以会导致在切换分支时工作区的内容不变:

为了解决这样的问题,我们需要用到下面的命名:
关于stash区命名:
git stash 将工作区中修改存储进stash
git stash list 查看存储在stash中的内容
git stash pop 将存储在stash中内容取出


当这个问题解决好后,我们回到最开始说的场景,我们在update分支上更新着代码,突然通知我们master分支上有bug需要我们维护,我们切换到master分支创建一条debug分支,我们在debug分支上解决bug,解决完后合并回master分支:

当debug完成后,我们继续回到update分支继续更新代码,此时由于master分支代码进行了debug和update分支初始状态不同,所以回存在合并冲突问题:
所以我们可以先将master分支合并到update分支上,先看看会不会有bug,如果检查发现没有bug,此时再重新合并到master分支中:


5. 远程操作
我们使用git进行版本控制的原因就是因为我们的程序很复杂,需要一个好的工具来管理我们的代码,而往往一个强大的程序是需要一个团队共同开发的,那么如何让众多程序员共同开发一个程序呢?这就需要通过远程仓库来进行了:
所谓远程仓库,不过就是一个存储在所有参与开发的开发者都能获取到和修改的计算机上的git仓库,众多开发者将自己的修改提交到这个远程仓库,从而使得这个远程仓库中的代码不断完善,这个远程仓库,可以说就是master分支,而我们不同开发者将远程仓库代码拉取到本地进行编码或者修改BUG,这些可以看作是创建子分支,这就是远程仓库;
复习用:
5.1 创建远程仓库
我们熟知的进行远程仓库管理的网站有github,gitee,其中github是国外的网站所以可能有一些网速问题,我们下面介绍gitee远程仓库创建的步骤:
5.1.1 创建操作
首先打开gitee网站:
然后进行登录与注册:

登录注册完成后,创建远程仓库:

进入创建页面按照下面内容填写:

如果想知道或者了解上面填写的基本内容可以看下面的5.1.2大致信息介绍,也可以自行百度,或者在gitee上查看他们的官方文档;
5.1.2 大致信息介绍
点击创建后查看仓库:
下面就是我们git远程仓库的基本样貌;
我们接下来先介绍一下我们创建仓库时所提到的内容;
在gitee平台上我们的仓库有这些成员:

成员权限说明:

其中,我们开发者如果发现了仓库中的bug或者有一些技术问题,可以通过issue进行留言:

issue是提供给开发者进行协作交流的一个方式;
pull requests:
介绍了基础的issue我们再来说说pull requests,它是用来进行merge申请的请求,因为一个公司中master分支上的代码是最为重要的,一旦进行修改如果发生产生了BUG,那将会是非常严重的问题,所以merge操作需要非常小心,所以这个操作是交给管理员进行的,开发者在编写好了代码后,给管理员一份pull request请求,管理员检验后进行merge操作;

了解了这些基本信息我们接下里继续进行远程操作;
5.2 克隆远程仓库到本地
我们回到远程仓库的页面:

首先,我们先来讲一下通过https的方式克隆仓库到本地;
5.2.1 https方式
我们复制https的clone命令:

粘贴到我们自己的机器上:

5.2.2 ssh方式
我们首先打开ssh克隆界面,复制ssh的url在我们需要克隆仓库的电脑上:

进行gitclone发现克隆失败:

为什么会这样呢,因为我们还没有配置ssh的密钥,我们怎么配置ssh密钥呢,我们继续往下看:
配置SSH密钥:
我们还是用gitee平台举例(所有平台方式都差不多),我们先打开我们代码托管平台的设置界面(我这里是gitee),然后找到shh设置相关:

那这个公钥我们要怎么添加呢?我们从哪里获得公钥填在这里?这时就要使用ssh-keygen命令了
创建ssh密钥:
指令:
生成 ED25519 算法的密钥对,注释为 "Gitee Key"
ssh-keygen -t ed25519 -C "Gitee Key"
生成 RSA 算法(4096位)的密钥对,注释为个人邮箱(-b 4096可以不加,用默认位数)
ssh-keygen -t rsa -b 4096 -C "alice@example.com"
上面两种不同的方式:
RSA :基于 大整数分解问题 的非对称加密算法,是SSH领域历史最悠久的密钥类型,依赖密钥长度(如2048/4096位)保障安全。
ED25519:基于椭圆曲线数字签名算法(ECDSA) 的优化变种,利用椭圆曲线的数学特性实现安全,属于更现代的加密方案。
不同加密方式的选择:
若需 兼容极老旧环境 (如内网古董服务器),可选RSA,若面向 现代开发/云服务(如Gitee、GitHub、Linux服务器等),优先选ED25519
还有很多不同的方式,我们如果感兴趣可以自行去理解;
选项:
-t <type>:指定对应的算法
-b <bits>:指定密钥位数,只能针对位数不定的算法
-f <file>: 指定密钥文件路径
-C <comment>:添加注释
执行指令:

输入指令后,我们一直按回车就可以了,我们也可以按照上面的提示输入密码 (不过这样我们使用ssh进行git可能还是需要输入密码),这样我们以后使用ssh进行git就不需要输入密码了;此时我们再查看.ssh目录下的文件,可以看到生成了公钥和密钥文件id_ed25519与id_ed25519.pub
查看生成的密钥:

.ssh目录linux下一般是再/home/usr(你的用户名)/.ssh,windows也是在用户目录

如果你生成后发现有其他的文件不用在意,他们不是你生成的你不关心即可;

我们主要不要把私钥,上面的id_ed25519暴露,注意是将id_ed25519.pub公钥进行设置;

设置ssh公钥: 
复制输出的公钥填写到设置界面;

填写你的代码托管平台账号密码,我们这里是gitee账号密码;

成功添加好ssh公钥到代码平台;

接下来我们再使用ssh方式来进行git clone就可以不输入密码成功克隆代码仓库了;
克隆仓库:

记住第一次clone的时候我们需要手动输入yes这样才能自动生成一个known_hosts文件

自动生成的known_hosts文件

我们在第一次进行ssh克隆后就不需要再输入密码即可进行克隆了;
known_hosts是 SSH 客户端(如 OpenSSH)的核心安全文件,主要用于存储已连接过的远程服务器的公钥信息,并在后续连接时验证服务器身份,防止"中间人攻击"(Man-in-the-Middle Attack)。它是 SSH 协议实现"主机身份验证"的关键机制。
SSH 连接的本质是通过加密通道安全通信,但第一步需要确认"你连接的服务器是你以为的那台"。known_hosts的核心作用就是记录已知服务器的公钥指纹,并在每次连接时比对,确保服务器未被篡改或替换。
这样我们就完成了ssh方式的git克隆;
5.3 向远程仓库推送
当我们本地维护好嘞版本库的时候,我们想让远程仓库看到我们的修改,此时就需要将本地仓库推送到远程了;
5.3.1 配置本地仓库
在文章最开始的时候,我们说过为了防止之后进行git时出现一些错误,先对git进行配置是必要的;
查看配置是否成功:
git config --list
配置user与email:
git config user.name "你的username"
git config user.email "你的email"
删除配置:
git config --unset user.name "你的username"
git config --unset user.email "你的email"
全局配置:
git config --global user.name "你的username"
git config --global user.email "你的email"
删除全局配置:
git config --unset --global user.name "你的username"
git config --unset --global user.email "你的email"
我们可以使用上面这些命令对git进行配置,而这些配置的原因是我们的每次提交要指向对应的用户与邮箱,这个可以对我们的提交进行定位,而我们设置全局的原因是默认当仓库没有设置局部配置时,默认使用的就是全局的配置,这样可以使得不需要每次创建仓库都进行配置提交信息;

可以看到我们的仓库中是存在全局变量的,然后此时我们再进行局部配置:

此时我们的当前git仓库是拥有全局和局部两个配置的,我们还可以使用--show-origin选项查看来源
查看配置来源:
git config --list --show-origin

上面带有/home/user路径的就是全局配置,其余是局部配置;
接下来我们就可以进行push操作了,而我这里想说这个配置的过程是因为,我想说明一下前面我们说到要进行配置的原因以及细节;
5.3.2 远程推送操作
git push完整语法:
git push [<远程仓库名>] [<本地分支>:<远程分支>]
示例:
git push origin master:master
简化格式(本地和远程分支同名):
git push origin master
推送到不同名称的远程分支:
git push origin local-branch:remote-branch
我们再执行add commit操作后,我们的修改已经提交到版本库了,此时我们就可以进行push推送修改到远程了;


在输入账号密码后,就成功实现了推送操作;

可以看到我们配置的用户局部的user.name显示到了提交处,我们点击还可以看到邮箱链接转到对应的邮箱进行联系,这就是配置账户git信息的作用;
5.4 拉取远程仓库最新状态
当多人协作开发时,仓库的分支是会持续更新的(如develop分支),当别的程序员新增或者修改好bug提交合并新代码到某个分支的时候,远程仓库的分支会发生改变,此时我们的本地仓库是不知道远程仓库的哪些分支发生了改变的,所以如果多人共同开发一条分支时,我们需要先进行git pull操作,来拉取远程仓库分支的最新状态;
(当然实际开发中我们是会创建一条独属于我们自己的分支然后merge到主分支上的)
5.4.1 同步分支代码
下面我们展示一下git pull的场景与使用pull的操作现象
程序员A与程序员B同时在master分支上编写代码时:

程序员A进行push操作:

程序员B此时无法看到A的修改:

此时A与B两人同分支下代码不同,如果B想要基于A的代码进行修改,此时需要进行pull操作;
程序员B进行pull操作,拉取最新分支:

此时程序员B就获得了最新的分支修改,可以基于此代码进行修改;
5.4.2 pull操作组成
pull的操作其实是fetch+merge组成的,我们可以查看上面pull成功后的输出;
复盘输出,证明拉取+合并操作:

我们此时如果使用git diff查看,我们是可以看到仓库没有任何修改,也就是暂存区,工作区,版本库都是相同的,所以说明了这个pull操作确实是包含了拉取+合并;
5.4.3 远程分支与本地冲突
当我们本地进行修改,而远程也已经被修改时,我们进行push与pull都是会有冲突的,git会阻止我们的操作,只不过进行pull时git会帮我们拉取远程的修改,然后有冲突时需要我们手动进行解决后进行提交,而push操作会直接禁止,让我们必须先进行pull操作后,再进行push操作;
下面是我们修改本地后,本地与远程出现冲突的现象;
如果我们先进行修改再进行pull:

git会终止我们这样的操作,让我们先进行add+commit后再进行pull;
如果远程分支修改,本地分支也同时修改:

git会禁止我们进行push操作,让我们先进行pull操作;
我们按照git的提示先进行pull操作,再进行push:
远程仓库代码:

本地操作:

本地操作分点分析:

1. 我们可以清晰的看到,如果我们本地与远程分支不相同,我们直接进行pull,git会将冲突写入我们的文件中,让我们手动进行解决,我们解决好之后继续进行push;

2. 我们继续进行push时会发现,我们依旧无法push,因为pull再fetch后进行merge时发现了冲突,所以让我们先手动解决冲突了,所以没有成功进行merge操作,导致我们当前本地仓库的分支其实是落后于远程仓库的;
**3.**我们再进行一次pull操作也可以发现,确实git输出说我们有未合并的文件,说明我们的merge操作还没有完成,我们解决冲突后还需要add+commit一次完成merge;
**
4.**所以此时我们继续进行add+commit,完成merge操作,我们再进行git push,这时我们可以看到,push操作成功了,本地与远程代码相同了
5.5 gitignore文件
这个文件是用来忽略当前git仓库中的某些文件的,有了这个文件,我们可以在add,commit操作时不会更新我们写入到.gitignore中的文件到暂存区与版本库,可以起到优化版本库结构的作用
这个文件创建仓库时可以一同生成:

那这个文件具体是做什么的呢,怎么起到优化的作用呢?
手动创建并文件编写:


.gitignore文件:
#这个文件中直接写你不想要添加到版本库中的文件(不想commit,add)
#这个#就类似于注释的意思
#我不想要所有md与py文件
*.md
*.py
#不想要指定的文件
.env
#在不想要的文件中例外需要的文件
#除此之外还可以使用git add -f need.md 命令强制提交
git查看现象:

查看文件被忽略原因:
查看命令:
git check-ignore -v <文件名>

这里可以看到是在.gitignore文件的第6行的规则的原因
5.6 git命令配置别名
我们在输入git命令的时候可能命令太长,我们输入起来太麻烦,这个时候我们可以自己给这个命令取别名来简化输入;
取别名命令:
git config --global alias.<别名> '<原命令>'
场景:

成功给branch命令取别名;
6.git标签管理
顾名思义,这一节是要讲解一个给commit提交打标签的作用,也就是给我们的版本库中的版本打标签;同时我们也会引出疑问,我们如何打标签,打标签的作用是什么呢?
6.1 标签操作
打标签我们可以理解为给重要的commit提交(版本库中的版本)做标记取别名,这样相对于比较难记忆的commitID,我们的标签更加有意义,让人更好记忆,方便我们进行版本切换操作;
我们先介绍一下标签常见的命令:
默认给最新一次提交打标签:
git tag [tagname]
给指定commit提交打标签:
git tag [name] [commit_id]
创建带有说明的标签:(-a指定标签名,-m指定说明⽂字)
git tag -a [tagname] -m "message" [commit_id]
查看标签信息:
git show [tagname]
查看所有标签:
git tag
删除标签:
git tag -d [tagname]
演示现象:
默认打标签:

指定commit提交打标签:

创建带有说明的标签与查看标签信息:

查看所有标签:

删除标签:

6.2 推送标签
我们还可以将标签推送到远程仓库;
推送指令:
推送单个标签:
git push origin <标签名>
推送所有标签:
git push origin --tags
推送现象:


删除远程仓库标签:
空推送(传统语法):
git push origin : <标签名>
--delete参数删除:
git push origin --delete <标签名>
删除现象:


7.多人协作
前面我们已经对基本的git操作进行了学习,接下来我们正式进入现实情况下git的使用------多人开发的场景,在这个场景下,我们仓库会有多条分支,分支之间的合并于冲突解决,版本的管理等等,都需要我们进行注意,所以接下来我们正式进入多人协作开发的场景;
7.1 clone时连接自动创建
首先我们新增一条分支:
模拟现实开发场景,你的同事新增分支修改代码;
我们查看一下本地分支(你的仓库)情况:

可以发现本地是察觉不到远程仓库创建新分支了的,我们需要进行git pull才能查看到;
讲解一下为什么git pull可以直接拉取到远程分支信息:

我们的git push操作,也是因为分支之间建立好了连接(在仓库进行克隆的时候master分支自动建立好了本地分支与远程分支的连接),所以可以忽略指定本地和远程分支名;
7.2 切换到远程分支
我们首先在远程仓库创建一些分支:

现实情况中是不会直接在远程仓库中创建的,是在本地基于某条分支创建之后再提交到远程的,我们这里为了方便就直接这么创建;
然后让模拟两个程序员进行协作开发:
我们介绍一下可能出现的几种情况
成员拉取到仓库后需要切换到远程的某个分支上:
标准切换操作:
git checkout -b [本地分支名] origin/[远程分支名]
小tip:
这里的origin(远程仓库名)也不是固定的,如果自定义了其他的远程仓库名那么这里就写对应的自定义名字,但一般默认为origin
简便操作:
git checkout [远程分支名]
我们可以直接使用上面两种方式切换到远程仓库中的某个分支;
1.标准切换:

2.简便切换:

简便切换其实是git自动做的搜索,他去搜索了远程分支中有没有和你当前切换的分支名字相同的分支如果远程有那么就新建本地分支并建立好和同名远程分支的关系;
7.3 手动连接远程分支
我们上面是成功的直接切换到了远程分支,但是如果我们是直接在本地仓库新建了分支,那么我们此时本地和远程仓库是没有建立连接的,所以我们是不能直接使用push操作直接push本地分支到远程的,此时我们需要手动建立本地分支和远程仓库中分支的关系;
查看分支关系命令:
git branch -vv
连接远程分支命令:
git branch --set-upstream-to=origin/<远程分支名> <本地分支名>

我们可以看到,当我们本地分支没有和远程分支建立连接时,我们使用与远程分支交互的命令,如git pull,git push这样的命令,都会有提示,我们只需要按照给的提示的命令输入,就可以完成连接远程分支操作,就像我们上面的图片中的输入,并且本地和远程分支的名字还可以不用一致;
7.4 PR请求merge合并
我们上面创建新分支,并修改了新分支的代码后,我们想要合并代码到主分支或者develop分支,此时我们如何合并呢,如果是我们个人我们可以先在本地把代码直接合并到develop分支或主分支,但如果是多人协作,你的代码需要被审核之后才能被合并到目标分支,所以此时,你需要进行PR操作,发起合并请求;
如何发起PR请求,我们可以看下面gitee平台的例子:
找到Pull Requests页面:

点击新建:

选择将合并分支:

红色框框中的数据需要按要求填写;
审核员合并分支(这一般是你的上级做的):

此时你就可以在你想要合并的分支看到你的代码了;但是往往合并的过程不是这么顺利的,说不定这个时候你的leader就会说,小张啊,你这个合并有冲突,你先去解决一下,这个时候我们就得手动解决冲突了,怎么解决呢?
7.5解决合并时冲突
我们先模拟一下冲突的场景,我们刚刚develop分支修改了,而此时如果基于原develop分支的另一个分支也新增功能,然后进行merge,这个时候就会出现冲突;
冲突场景:
假设我们一开始的develop分支的内容是111,这是develop分支的稳定内容,这里的功能是稳定的前人开发好的:

然后你们部门,在一次会议上讨论,拟定方案,你们需要新增两个功能,这个时候张三和李四他们被分配来写这两个不同的功能,张三负责写的功能是222,李四负责写的是333功能,于是他们都拉取了develop分支,开始在这个分支上新增功能;
首先切换到develop分支,然后创建自己的新增功能分支
张三开始新增功能:

李四开始同步新增:

张三开始新增功能并提交代码到自己分支:

张三请求合并到develop分支:
然后张三测试并验证了自己功能没有问题,部门的测试也验证了这里的功能没有问题后,张三开始提PR(pull request),请求合并分支到develop分支上:


mentor审批张三代码:
mentor在检查了代码后,认为张三的新增代码还不错,同意了这次请求;



此时张三的功能成功被合并进入了develop分支;
李四开始合并代码到develop分支:
但是与此同时李四也在开发功能333,他的功能要复杂一些,他在开发的过程中并不知道张三已经提交代码到develop分支了,此时李四还是基于旧的develop分支开发的,然后李四将将新增功能进行测试通过后,也提交了PR:


李四提交完PR后,这个提交到了mentor这边,然后这边mentor看到了这里PR,发现了冲突;
mentor审批李四代码:

此时mentor对李四说,你这边代码存在冲突,你先去解决一下冲突吧;
mentor让李四解决冲突:
于是李四这边切换到develop分支使用git pull拉取了一下,发现这边确实他的分支不是最新了的,然后他这边再切换回他自己的分支进行一次merge操作,将develop分支合并到自己的分支看看有哪些冲突:

查看到这些冲突后修改冲突并再次提交:

李四解决冲突后提PR:

此时mentor再查看,这回就可以合并分支了;
此时再切换到develop分支就可以看到新增了功能222和功能333的全部代码了:


此时就完整实现了一次企业的功能新增的完整流程,其实看到这里你就已经完全掌握了企业级的git版本控制能力了,可能只是还有一些git命令你需要学习一下,便利你的git使用而已;
8.企业分支模型
8.1分支概览
| 分支 | 类型 | 生命周期 | 用途 |
|---|---|---|---|
| master | 主分支 | 永久 | 生产环境代码,始终可发布 |
| develop | 主分支 | 永久 | 开发集成分支,记录所有开发人员提交 |
| feature/* | 临时分支 | 短期 | 需求开发 |
| release/* | 临时分支 | 短期 | 测试分支,发布前验证 |
| hotfix/* | 临时分支 | 短期 | 生产环境紧急修复 |
8.2分支详解
1.master
生产环境唯一代码来源
仅接受 release 和 hotfix 分支合并
每次合并需打 tag 标记版本
2.develop
开发主干,集成所有已完成功能
所有 feature 分支从此拉出并合回
代码相对稳定,可随时创建 release
3.feature/*
命名:feature/功能名 或 feature/JIRA-123-功能名
从 develop 拉出 → 开发完成 → 合并回 develop → 删除
一个功能一个分支,互不干扰
4.release/*
命名:release/v1.0.0
从 develop 拉出 → 测试修复 → 合并到 master 和 develop → 打 tag → 删除
仅允许 bugfix,禁止新功能
5.hotfix/*
命名:hotfix/问题描述
从 master 拉出 → 修复 → 合并到 master 和 develop → 打 tag → 删除
用于生产环境紧急问题
8.3工作流
master ─────────────────●───────────●─────
↑ ↑
release/v1.0 hotfix
↑
develop ──●────●────●────┘
↑ ↑ ↑
feature branches
8.4分支与环境
| 分支 | 环境 |
|---|---|
| feature/* | DEV |
| develop | SIT |
| release/* | UAT |
| master | PROD |
9.拓展
这里我会不断添加我认为可以辅助我们更为规划的使用git的文章,我们集思广益,一起进步:




