G

[Git学习] Git相关

RoLingG 其他 2024-04-16

Git相关

序言:

Git中有下面两个重要的概念。

仓库,仓库可以被理解成一个目录,仓库里的每个文件都有完整的版本历史记录,所以仓库里每个文件的修改、删除、添加等操作,都会被Git记录下来,因此可以看到是谁在哪个时间修改了哪些文件的哪些内容。这样做以便于任何时候都可以追踪历史或者还原到之前的某一个版本。

创建仓库非常简单,使用git init命令就可以将一个目录变成仓库。一般来说两种方式创建仓库,一种就是上面的git init在本地目录创建,一种是git clone在远程服务器上克隆别人上传的仓库。

版本控制系统,可以帮助用户跟踪每个文件的变化,让项目成员的协作更加高效。分为集中式版本控制系统和分布式版本控制系统,Git就是分布式版本控制系统。

集中式结构

请输入图片描述

引用自B站Up主:GeekHour

所有文件都保存在顶部的中央服务器内,每个客户端都只能存放一个文件副本。当需要修改文件时,需要从中央服务器内下载最新的文件,再进行修改,传回中央服务器。

简单、有效,但同时也不能有效处理好单点故障问题,中央服务器如果故障或连接出问题,那就完了。必须修复好中央服务才能让所有人进行正常的使用。

分布式结构

请输入图片描述

引用自B站Up主:GeekHour

每个客户端都能有完整的版本库。这使得每个人都可以自己本地进行修改,再上传到中央服务器。即便中央服务器出问题了,每个客户端上都还有自己的版本库,可以边等中央服务器修复好,边修改自己的文件,修好了再上传上去。如果我们需要将修改内容分享给别人,只需要上传到仓库,再让别人同步一下就可以了。

现在因为Git免费开源、速度快、功能强大、支持离线、分支管理等优势,使得Git成为世界上主流的版本控制系统。

Git的工作区域和文件状态

工作区域

Git本地区域管理分为:①工作区、②暂存区、③本地仓库

工作区又叫工作目录,也就是本地目录。

暂存区又叫索引,是一种临时存储区域,用于保存即将提交到Git的工作内容。

本地仓库,就是使用git init创建的本地仓库,拥有完整的项目历史与元数据,是Git存储代码和版本的主要位置。

综上:工作区即.git目录所在的目录;暂存区为中间区域,暂存要提交的内容;本地仓库就是Git存储代码和版本信息的主要位置。

实际工作中,文件流为:工作区→暂存区→本地仓库。工作区→暂存区使用git add将文件从工作区添加至暂存区;暂存区→本地仓库使用git commit将文件从暂存区提交到本地仓库。

文件状态

Git文件状态分别有:①未跟踪、②未修改、③已暂存、④已提交。

一些基础语法:

git init 创建Git仓库

git init xxx 创建指定名字的Git仓库

git config --global user.name "xxx" 配置用户名

git config --global user.email xxx@xxx 配置用户邮箱

git config --global credential.helper store 保存用户名和密码

git config --global --list 查看Git配置信息

git add 将文件添加至暂存区

后面可以跟./xxx(文件名) .表示当前目录下,xxx表示具体文件

git log 查看Git中操作记录

git log --oneline可以看简易的提交记录

git status 查看仓库的状态

git status -s 查看仓库简略的状态信息

一般来说会有??,第一列为暂存区的内容,第二列为工作区的内容。M为被修改过的文件。

git ls-files 查看暂存区的内容

git ls-files:列出当前Git仓库中跟踪的文件,包括已修改/已缓存/已提交的文件

git commit 提交暂存区内的文件至仓库

git commit -m "xxx" 指定提交的信息,如果不用-m则会进入交互界面来进行编辑提交信息。

git commit -am "xxx" 指定提交的信息,同时完成添加至暂存区和提交至仓库两个操作。要注意的是,这只能第一次提交时候,后续提交不行。

git reset 撤销提交

git reset --soft 回退到某一个版本,保存工作区和暂存区的所有修改内容

git reset --hard 回退到某一个版本,删除工作区和暂存区的所有修改内容,所用场景一般为要抛弃当前本地所有的修改内容,谨慎使用

git reset --mixed 回退到某一个版本,保留工作区的修改内容,删除暂存区的修改内容

--mixedgit reset的默认参数。

上述操作都不用担心误操作,因为Git中所有的操作都是有记录的,可以使用git reflog进行查看历史操作记录,找到误操作前的版本号,然后git reset回那个版本就行。

git diff

可以查看工作区、暂存区、本地仓库之间的差异,还可以查看文件在两个版本之间的差异,或者文件在不同分支之间的差异。、

git diff命令输出第一行为发生修改的文件;第二行为文件内容,Git会将该文件内容会使用哈希算法生成一个40位的哈希值,一般只显示前7位,空格后面的数字为文件的权限;第三行之后就是文件修改的内容。

git diff 看工作区和暂存区的文件差异

git diff HEAD 看工作区和仓库的文件差异

git diff --cached 看暂存区和仓库之间的差异

git diff 版本id1 版本id2 看两个版本之间的差异

注:HEAD指向分支的最新提交节点。HEAD~/HEAD^表示最新提交节点的上一个版本。HEAD~2这种带数字的,就表示最新提交节点的上几个版本。此外在这后面加文件名就可以只查指定文件的差异内容。

git rm xxx 删除某个文件

另外:

这条命令和直接在工作区中删除文件后使用git add更新那个文件被删除是一样的,git rm xxx会将工作区和暂存区指定名为xxx的文件一起删除掉。提交之后就完全更新完了。

git rm --cached xxx 把文件从暂存区中删除,但是不删除工作区对应的文件。

git rm -r会递归删除指定目录下所有的子文件。

注:

在一个目录下git init后,这个目录内会生成一个.git的隐藏目录,里面存放着在这个目录下Git仓库的重要信息。如果删掉,那么这个目录下的仓库也会随着被删除。

注:git initgit init xxx这两条命令有一定的区别,git init是在当前目录下创建仓库,而git init xxx是在xxx目录下创建仓库,也就是说在当前目录下如果没有名为xxx的目录,那么在使用git init xxx这条命令后,该目录下会自动创建一个名为xxx的目录,并将在该目录下创建仓库。

另外:git clone xxxgit init xxx一样,也是会自动创建对应名字的目录并将远程仓库克隆到生成的目录下。

.gitignroe忽略文件

这个文件可以让我们的版本库忽略掉一些不需要的文件。使得我们的版本库体积小、干净。

它一般会忽略:①系统自动生成的文件(例如一些软件产生的临时文件)、②编译生成的一些中间文件和结果文件(一个文件是另一个文件自动生成的,那么自动生成的这个文件会被忽略)、③运行时的日志文件、缓存文件、临时文件等、④涉及敏感信息的文件(例如秘钥、口令等敏感信息文件)。

只要我们在.gitignroe文件中配置好要忽略的文件,那些文件就不会出现在版本库(仓库)中。

但是.gitignroe文件内忽略配置生效有个前提,就是它要忽略的文件不能是已经被跟踪的文件,如果仓库内有要忽略的文件,但是被跟踪着,那么对这个要忽略的文件不会生效忽略配置。并且后续对要忽略的这个文件进行正常Git操作也是行得通的。

某个文件已经被提交到仓库中,然后将它添加到 .gitignore 中,Git 会忽略该文件的更改,但仍然会跟踪它。

出现上面这种情况,我们只需要通过git rm --cached命令将要忽略的文件从暂存区删除,这样那个文件就会从被跟踪状态 → 未被跟踪状态。这时候.gitignore文件就可以生效忽略它了。

另外,文件夹也可以作为.gitignore文件忽略的对象。文件夹的格式以/结尾。

如果文件夹是空的,那么它不会被纳入仓库的对象,不会进入版本控制中。

匹配规则

.gitignore文件是按行从上到下进行匹配的,每一行表示一个忽略模式。

请输入图片描述

引用自B站Up主:Geekhour

另外:

其实.gitignore文件不用全部自己写,Github上已经有很多人将自己的.gitignore文件模版给分享出来了,包含各种语言。我们要做的就是拿过来改改便可以自己用了。

远程仓库

创建远程仓库

全世界现在最多人用的远程仓库便是:Github。

当然也有类似coding、gitee国内远程仓库以及国外的GitLab等远程仓库,都大差不差。

其中GitLab能够私有化部署,能够部署服务器,在服务器上进行代码管理,安全性高。(GitLab官方也提供了Docker部署到本地的方法)

创建远程仓库可以使用SSH方式,但是SSH方式要使用密钥,有点麻烦的同时也保证了更高的安全性(当然Github的方式也只剩SSH)。

Linux中,我们先cd .ssh,然后使用ssh-keygen -t rsa -b 4096生成一个大小为4096的rsa秘钥,之后输入密钥名称便可获得密钥。不输入名称默认密钥名为id_rsa

生成完之后会有一个秘钥名本身的文件和一个密钥名.pub的文件,前者是私钥后者是公钥,私钥不要乱给被人,公钥输入进Github个人配置就行。

如果指定名称生成密钥,那么就要生成一个如下内容的config文件

#github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/秘钥名

进行配置,这样Github才能访问我们的密钥。

同步本地仓库和远程仓库的命令:git pullgit push

git pull 将远程仓库的内容拉取到本地仓库

git pull <remote> 拉取某远程仓库到本地仓库

git push 将本地仓库的内容推送到远程仓库

git push <remote> <branch> 推送某本地仓库到远程仓库的某个分支,默认分支为main分支。

本地仓库关联远程仓库

一般来说创建仓库的时候,下面都会有提示代码告诉你怎么关联仓库(Github是有,我记得gitee也有)

关联之后使用git remote -v可以查看当前本地仓库所关联的远程仓库的别名和地址。

git push -u 远程仓库别名 main:main 将本地仓库的main分支与对应别名远程仓库的main分支关联起来。

以此类推:git push -u 远程仓库别名 远程仓库分支名:本地仓库分支名

如果本地分支的名称与远程分支的名称一致,则可以省略一个,即git push -u 远程仓库别名 main

git pull 远程仓库名 远程仓库分支名:本地仓库分支名 拉取对应远程仓库对应分支内容到本地仓库对应分支内。

这里仓库名和分支名可以省略,省略了之后默认拉取仓库名为originmain分支。默认作用就是将远程仓库的分支拉取到本地进行合并。

从远程仓库拉取文件过来Git会默认帮我们进行一次合并操作,如果两个仓库之间文件没有冲突,则合并成功;如果有冲突,则会报冲突错误,并合并失败。

git fetch 只获取远程仓库的修改,不自动合并到本地仓库中。

即放到本地仓库的暂存区,要我们手动合并。

请输入图片描述

分支

分支利于开发团队进行开发工作,开发人员可以自建分支,在分支上进行自己的开发工作,也可以随时创建一个分支进行功能的开发或者Bug修复,使得主枝干代码仓库处于一个随时可用的稳定状态,等分支上工作完成之后再将该分支的内容提交到主分支上,不影响其他人开发的同时,保证项目高效稳定的正常运行与协作。

分支的有效利用能够减少团队开发的冲突和错误影响,实现高效合作,让团队的每个人都能够独立开发和测试。

使用场景例如上级交给我们一个新的功能需求,这样我们就可以新建一个分支去专门开发这个功能需求。

git branch 查看当前仓库的所有分支

git branch <branchName> 在当前仓库创建一个指定名字的分支

git checkout <branchName> 切换到指定名字的分支

使用git checkout命令切换分支可能会出一些问题,因为这个命令还可以用来恢复文件或者目录到之前的某一个状态。

例如:git checkout -b 分支名 该分支某个提交节点的ID 这样做就可以将该分支恢复到指定的一个时间点的状态。这个提交节点ID可以用git log相关命令看,用GUI工具也可以很方便的看到。

如果使用这个命令的时候分支名和文件名相同,会出现命令操作歧义。git checkout默认是切换分支而不是恢复文件,所以可能想使用这个命令恢复文件的时候会出点小问题。

git switch <branchName> 专门用来切换到指定名字的分支

上面的问题在Git现在版本中被git switch这条命令解决了。

git merge <branchName> 将指定名字的分支合并到当前所在分支

使用该指令后,会自动有一次自动的提交。

且合并后,被合并的分支依然存在。

git log --graph --oneline -decorate --all 查看当前仓库的分支图

这个命令挺长的,如果每次使用都要输这么长的命令肯定麻烦。

所以可以使用alias命令将它变为一个指定的短词来进行快速的使用

git branch -d <branchName> 删除已经完成合并的指定名字分支

git branch -D <branchName> 强制删除指定名字分支

分支的管理是有必要的,这能大大的减少分支带来的错误,从而加快开发的效率。

git rebase命令也可以合并分支。

git merge命令合并的分支就像枝干河流与主干河流交汇合并一样,枝干合并到主干后,会在主干上留下一个节点。而且git merge命令必须要在main分支上进行。

rebase操作不一样,我们可以在任意分支上使用rebase操作。我们在分支上使用git rebase main,那么这条分支就会被变基main分支上(变基,也就是将当前分支直接从头移接到main分支的尾部)。如果在main分支上使用git rebase 其他分支名,那么main分支与这条其他分支的交错点之后的main分支部分就会移接到其他分支的尾部。

请输入图片描述
请输入图片描述

根据上面的描述,我们会发现rebase操作造成的合并后的节点顺序与merge操作不一样,这是因为rebase操作的原理导致的。在Git的分支中,每个分支都有一个Head指针,都指向着当前分支的最新提交记录。而在执行rebase操作之后,Git会找到当前分支和目标分支共同的父提交节点,再把当前从分支共同父提交节点之后到最新提交节点这一串子分支都移接到目标分支的最新提交节点之后。

  1. merge操作

    优点:不会破坏原有分支的提交记录历史,方便回溯与查看。

    缺点:自由度被限制且会产生额外的提交节点,分支图比较复杂。

  2. rebase操作

    优点:不会新增额外的提交节点,形成线性历史,比较直观干净。

    缺点:破坏了原有分支的提交记录历史,改变了当前分支branch out的节点。

综上,rebase操作避免在合作开发中使用,容易混乱同分支上其他开发成员的开发进程。

Pull Request四种合并方式

说到这里,就不得不提一下Git操作里面Pull Request的四种合并方式以及它们的特点和之间的区别了。

  • Merge Pull Request(Merge合并)

    这种方式会将 PR 中的每个提交合并到目标分支中,同时创建一个新的合并提交(merge commit),这个合并提交会有两个父提交:一个是 PR 分支的最后一个提交,另一个是目标分支在合并之前的最后一个提交。

    这种方式保留了 PR 分支的完整提交历史,但会导致目标分支的提交历史中出现多个分叉,可能会使得历史记录变得复杂。

  • Squash and Merge(Squash压缩合并)

    在这种方式下,PR 中的所有提交被压缩成一个单独的提交,然后合并到目标分支中。这个新的提交会有一个清晰的提交信息,通常是 PR 的标题和描述。

    这种方式有助于保持目标分支的提交历史整洁,使得后续的代码审查和维护更加容易。但是,它不会保留原始的提交者信息和提交历史。

    注意:对于普通的Merge而言,在当前分支上的合并提交通常会有两个parent;而Squash Merge却只有一个。

    应用场景:比如改正一个小小的拼写错误可能也会成为一个独立的提交,而我们并不希望在合并时把这些细节都反应在当前分支的提交历史里。这时,我们就可以选择 Squash and Merge 合并方式。

  • Fast-forward Merge(Fast-forward快进合并)

    当 PR 分支直接基于目标分支的最新提交时,可以使用快进合并。这种方式实际上是将目标分支的指针直接移动到 PR 分支的最新提交,没有创建新的合并提交。

    这种方式适用于 PR 分支是目标分支的直接后继,它保持了提交历史的线性,但是不会在目标分支上留下合并的痕迹。

    注意:这种将指针变更的方式虽然效率很高,但是要求参与合并的两个分支上的提交必须是“一脉相承”的父子或祖孙关系。

    同时这个方式有个缺点,作为被合并的开发分支,它的提交历史在合并以后会和master分支的提交历史重合。

  • Rebase and Merge(Rebase变基合并)

    这种方式首先将 PR 分支的提交重新应用到目标分支的最新提交之上,然后再进行合并。这样,PR 分支的提交历史会被整合到目标分支的末尾,形成一个线性的提交历史。

    这种方式同样可以保持提交历史的整洁,但是它可能会改变原始的提交顺序和提交信息,因为每个提交都会被重新创建。

    注意:一般来说使用rebase方式合并的Pull Reuqest是无法回退的,所以要确定好合并内容没问题之后再使用该方式合并。

    rebase合并的 PR 不能回退是它本质原因导致的,因为rebase 实际上是将一系列提交按照原有次序依次应用到另一分支上,而不是将最终结果合在一起。如果你在 main 分支上进行了 rebase 合并了 dev 分支,然后推送到远程仓库,其他人基于你 rebase 后的 main 分支进行开发,这时你再想回退到 rebase 之前的状态就会很困难,因为你已经改变了历史,新的提交都是基于 rebase 后的新基线创建的。

    相当于你开车变道到一条没有退路的道路,原来的道路消失了,而你倒车发现没有退路。这就是 rebase 合并不能回退的原因。

不同的合并方式可以根据不同的场景发挥它们的作用,如果希望保持目标分支历史的清晰和整洁,可能会选择 Squash and MergeRebase and Merge。如果希望保留完整的提交历史,可能会选择 Merge Pull Request。而 Fast-forward Merge 则适用于没有冲突且 PR 分支是目标分支直接后继的情况。

合并分支如何解决冲突

一般情况下,如果要合并的两个分支的修改内容没有重复部分,那么Git自动合并会顺利许多;但如果两个分支都修改了同一个文件的同一行代码,那么Git自动合并这时候就会合并失败,产生冲突并报错。

这时候自动合并就不行了,要让我们自己手动合并。产生冲突后,我们可以使用git diff查看两个分支的修改内容,通过查看修改内容来进行冲突的修复,也就是修改这个冲突文件的内容。

工作流模型

常用工作流模型:Gitflow模型

Gitflow模型分为①主线分支(main/master)、②问题修复分支(hotfix/bugfix)、③开发分支(develop)、④功能分支(feature)、⑤版本发布分支(release)

主线分支(main/master分支)

主线分支的代码必须被保证是能随时发布的。主线分支的代码一般不能直接修改,只能通过分支合并的方式进行修改。

每次合并分支都建议新增一个新的版本号,方便后续追踪与版本回溯(通过git tag命令来标记版本号 )。

版本号规则一般也是有规定的。

主版本:主要的功能变化或者有重大更新

此版本:一些新的功能、改进和更新,通常不会影响现有功能

修订版本:一些小bug的修复,补丁等,一般不会更改现有的功能与接口。

问题修复分支(bugfix分支)

一般包含了项目内某个问题修复的源码,用于热修,也就是修复线上问题。一般来说问题修复分支是从主线分支分离出来的,修复完成后会合并到主线分支和开发分支内。合并完后会将该分支删除掉。

问题修复分支修复完之后,也会更新项目的修订版本号,也就是小小版本更新。

开发分支(develop分支)

从主线分支分离出来,包含了项目开发的最新源码。

该分支用于开发与测试,是一个重要的分支,长期存在。

功能分支(feature分支)

一般包含了项目新功能的最新源码。一般来说一个项目会有多条功能分支,都是从开发分支中分离出来。当功能分支的代码稳定后,便会合并到开发分支中。

一般来说问题修复分支的内容在经过测试验证没问题之后,就往功能分支合并,然后功能分支再将这些改动合并到开发分支(也可以是创建一个基于上个开发分支的新版本开发分支,做好开发分支迭代更新和更改记录备份,以便于开发人员在一些情况下想要了解上次更新了什么内容)

版本发布分支(release分支)

该分支包含了项目最新发布版本的源码,用于发布前的测试与校验。一般来说版本发布分支是从开发分支中分离出来的,当该分支的代码稳定后,会合并到主线分支和开发分支中。合并完之后会将版本发布分支删除掉。

其他

分支时间周期

根据上述分支描述,我们可以将分支分为主要分支辅助分支,像开发分支和主线分支,我们就称之为主要分支。像工能分支、版本发布分支等这些时间周期短,合并后删除的分支又称为辅助分支

由于GitFlow模型需要开发团队具有一定的流程开发能力和规范能力,所以并不是所有开发都需要使用该模型。大多数一般都会用GithubFlow这种简易的模型。

GithubFlow模型主分支的代码可以直接部署到生产环境中,一般禁止团队成员直接在主分支上提交,团队成员可以在主分支上分离出自己的分支进行开发与测试,等开发完成后便向主分支发出pull请求,主分支同意pull请求之后,便将该分支内容与主分支合并。

规范

另外,命名规范在开发中也比较重要。

分支命名应该使用带有意义的描述来当分支名,这样能让同项目开发人员更好的辨认出分支里是什么,方便管理。

例如:

  • 版本发布分支应该用版本命名。
  • 功能分支应该用功能名命名。
  • 修复分支应该根据修补问题的编号命名。
分支创建与适当的删除也很重要。
  • 定期合并已经稳定的分支,及时删除已经合并的分支。
  • 保持合适的分支数量。
  • 为分支设定合理的权利。
PREV
[算法学习] 找出字符串中第一个匹配项的下标
NEXT
[Redis学习] Redis缓存雪崩、穿透、击穿问题

评论(0)

发布评论