〇、写在前面

在了解Git是什么之前,不妨来想想我们需要它来做什么?

在企业开发项目时,我们通常需要很多成员共同开发一个项目的许多功能,而整个项目的开发也要经过许多次迭代优化不断修改。如果没有一个协助我们的工具,事情会变成什么样?

  • 作为一个正在开发功能的团队成员,你需要一直保存你自己现在编写的成果的副本,以免某一天想找回的时候却发现没有回头路了。于是你只能不断的保存一份又一份的文件,并修改它们的名字,给它们加上“副本1”、“副本2”、“xx月xx日版本”这样的标识来区分。而随着开发的推进,这样的文件逐渐堆积,把你的文件夹搞的杂乱无章毫无条理,而一段时间之后的你也不太能借助之前写的标识来区分这些文件哪些更加重要,于是也不敢随便删除这些文件,只能继续保持这种杂乱的状态继续下去

  • 当你和其他成员同时开发功能时,你们都复制了一份现有版本的代码单独开发,在各自的电脑上编辑之后你们总算都整理了一份完整的成果出来。但是这个时候出现了一个问题,团队需要你们把你们的功能都合并到一起时,你们都无法确认对方修改的部分是不是对另一方的内容有影响,于是你们只能坐在一起详细核对各自修改的部分能不能直接插入到现有版本的文件夹里面,直到把两边的修改部分都核对完成为止

这样的问题在协作开发时几乎不可能避免,尤其是在代码开发时,小功能的迭代速度十分迅速,参与开发的程序员数量甚至可以达到几千甚至数万,这个时候就必须要使用版本控制的工具来帮助我们保存好开发中的各个版本的文件来保证开发的稳定,同时借助工具的版本控制功能协调好几个可能出现冲突的版本的融合和协作。

于是乎,相应的产品就从待解决的问题之上诞生而出了

一、什么是Git

在开始详细的介绍之前,不妨使用两句话直接给出这个问题的答案:

来自Git官网的描述

Git 是一个免费且开源的分布式版本控制系统,设计用于以速度和效率处理从小到非常大的项目

Git 易于学习,具有极小的资源占用和闪电般的性能。它凭借廉价本地分支、便捷的暂存区域和多种工作流程等特性,在 Subversion、CVS、Perforce 和 ClearCase 等 SCM 工具中脱颖而出

作为一个“版本控制系统”,Git这样的工具就是帮助我们解决先前出现的那些问题的,具有这样功能的软件工具我们就可以把它叫做“版本控制系统”(version control system)。

而“分布式”这一概念则就体现了Git的不同之处了。其实不难发现,先前提出的问题是在过于基础,几乎是一进行团队开发就完全不能避开的,所以在Git之前就已经有一些版本控制系统的工具问世了。

CVS、SVN这些就是相当早出现的版本控制系统,尽管这样的工具有部分是免费开放的,但是由于它们的集中式设计,开发中的各个版本都必须统一存放在一个中央服务器中,版本的保存和协调都需要借助中央服务器来操作。而这样的设计问题也很明显,每一次版本操作都需要向中央服务器进行请求,中央服务器在面对人员数量更大的团队时就会忙于同时出现的操作请求,从而响应变慢,服务的性能还严重受到网络情况的影响。

Git的创造者Linus最初在管理自己的Linux项目时,就通过全世界开发者将源代码文件通过diff的方式集中发给他,然后由Linus他本人通过手工方式合并代码的方式来管理项目的版本。哪怕后来有免费的集中式版本控制系统可供使用,但由于他本人对这样的集中式系统设计性能低下的反对,Linux项目的开发也都没有使用集中式版本控制系统。

但是随着项目的变大,传统的管理方式下Linus的压力大到已经不能支持起Linux项目的管理了,于是Linux尝试使用了BitMover公司免费提供的付费商用版本控制系统BitKeeper。但是开源社区的程序员们总是个性独特,不断尝试破解BitKeeper的协议,这引发了BitMover公司的不满,收回了Linux开源社区使用BitKeeper的权限。

这一契机没有使得Linus本人向BitMover公司道歉,反而激起了他开发新版本控制系统工具的热情。两周之后,他就拿出了使用C语言开发的Git工具,并在一个月之内将Linux的项目代码移动到Git的版本控制之下。

Git的开发从一开始就舍弃了集中式的设计,使用了更加开放的分布式设计思想,整个版本控制系统不存在“中央服务器”,每个人的电脑上都是一个完整的版本库。而整个多人协作只需要成员之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。而在高效的分布式系统中,也会出于方便设置一个服务器用于统筹各个网络下的版本交换,相比于集中式的中央服务器,这样的服务器只需要负责交换而非操作,效率也大大提高。

二、怎么用Git

Git的安装在互联网上参考很多,很多给出了详细的安装流程和指导,这里不多赘述。

而使用Git的关键更多在于理解Git的设计和指令并在实际的项目中使用,下面将对这一部分进行说明和总结

1. Git的架构设计

现在,我们可以先花一点时间想想,你应该设计一个什么样的存储结构来保存你的版本并于你的当下开发版本做分离

Git给出了它的答案如下面的图片所示:

Git存储结构(来自B站UP主Zhengyaing制作)

在一个受到Git进行版本控制的项目中,你现在能够操作的所有文件夹和文件构成了Git的工作区Workspace/Working Directory)。工作区直接与你的电脑文件结构映射,你对文件的每一次编辑都直接相当于更改了工作区的文件(或者说改变了工作区)。

Git将你的历史版本都保存在本地仓库Local Repository)中,这里存储着真正的历史信息,它就像它的名字一样,像一个仓库保存好了你每一次放进去的版本状态以供你读取和回溯。

在两者之间,Git还设计了一个中间的过渡区域——暂存区index/Stage Area)。来自工作区的修改可以被暂存在这里,等待你的一次提交打包送入到本地仓库中存储。

而在现代的Git使用中,我们常常会将本地的Git与远程平台进行配合,由他们负责在云端管理和存储我们的Git本地仓库(就像一个网盘一样)。这个远程平台上搭建的你的仓库的一个副本就是远程仓库Remote Repository)。你可以在远程平台设置相关的配置,将你的仓库开放给其他人,大家协作开发,把自己的各自在本地的仓库同步到远程仓库上,从而共享工作进度完成整体配合。

在Git的设计思想中,我们可以将整个Git管理的过程想象成生产到仓储的过程:我们在自己的工厂(工作区)进行生产,对于生成中暂时确认完成的工件,我们可以将它放到运输的卡车(暂存区)上,如果有一批次的工件已经放到了卡车上并且我们确认这一批次的工件可以相对独立的整理到一起,就可以让卡车把工件送(提交/commit)到仓库中放起来,对于卡车送的这一批次的工件,我们把相关信息写成一个批次单(comment)和工件放在一起以便我们在仓库中查看。这样的一个流程就是Git管理流程的简单分析,理解了这样的操作,我们就可以更加方便的接受后面的指令操作。

图中的其他部分我们在后续的内容中再继续介绍,有了以上四个组成,我们就可以完成基本的Git使用了。

2. Git的本地管理流程

在你需要管理的文件夹下,打开终端输入指令初始化一个Git仓库,这是Git版本管理的开始

git init

执行指令之后,我们可以发现在当前文件夹之下出现了一个隐藏的文件夹.git/ ,这个文件夹内保存了Git存储的版本信息,Git整个仓库管理的数据都保存在其中,如果删除掉这个文件,Git版本管理的所有内容都会丢失(与没有建立仓库一样)。

这时我们可以使用后续常常使用的指令来查看整个Git的工作状态

git status

执行指令之后,我们可以拿到当下的状态信息

在实例仓库下的git status输出结果

在一个只有一个文件file1.txt的文件夹执行上述两步操作的输出结果如上。我们可以发现,Git告诉我们,我们处于分支master上,还没有commit,当前有一些没有被追踪的文件(Untracked files):file1.txt ,并在最后总结了一句话:没有内容要提交,但存在未跟踪的文件。

联系上一节的架构设计,我们来解释一下前两步的操作

随着Git仓库的建立,工作区与你当前的文件夹完成映射,工作区内和当下文件系统的结构一致,在我们的案例中,工作区中现在有一个file1.txt文件。而初始化之后,Git的暂存区和本地仓库现在都是初始的空状态,这两个区域中现在什么都没有。

Git将你的工作区与暂存区和本地仓库(现在这两个是空的)比对发现,你有文件更改没有被记录到仓库中,甚至仓库内都不知道有这些文件,于是Git把这样的文件记录为Untracked

按照Git的架构设计,我们将工作区内部存在且与暂存区和本地仓库不同的文件通过指令添加到暂存区(这是通往本地仓库的过渡)

git add file1.txt

git add指令的格式为git add <文件名> ... ,我们可以在add后面填写上需要从工作区添加到暂存区的的文件的名称

同时,add后的参数还支持目录和通配符操作。我们可以使用表示当前目录的.代替文件名,这样表示将当前目录下的所有内容都添加到暂存区中。而如果我们想要对某一些特定的文件进行操作,也可以使用通配符的方式,比如使用*.cpp 标识所有的cpp后缀的文件

在我们的案例中,当前目录下只有一个file1.txt文件

  • git add file1.txt # 添加file1.txt文件到暂存区

  • git add . # 添加当前目录下的所有文件到暂存区

  • git add *.txt # 添加所有以后缀为txt的文件到暂存区

以上三种指令的效果在案例中是一致的

此外,如果我们想将暂存区保存的更改回退到工作区中,我们也可以使用git add 的逆向指令git reset

我们可以按照git reset <文件名> 来回退暂存区中的某一个文件或者使用git reset 直接回退整个暂存区

执行完添加指令之后,我们可以再通过git status指令查看当下的仓库状态:

这次我们发现,Git告诉我们的不再是“没有内容要提交,但存在未跟踪的文件”,而是变成了“有更改等待提交:新的文件: file1.txt

这说明Git对比了工作区、暂存区和本地仓库的内容,发现我们已经在暂存区跟踪了一个文件file1.txt,但是在本地仓库中还没有这个文件,所以这是一个“有待提交的新的文件”

那么到现在为止,暂存区中已经有了file1.txt的文件更改信息,而本地仓库中仍旧为空

接下来,我们假设现在暂存区内保存的更改已经达到了可以保存到本地仓库的标准(我们自己在开发的时候认为),我们就准备使用指令将暂存区的内容打包提交到本地目录去

git commit -m "commit new file:file1.txt"

git commit 指令的格式为git commit [选项] [信息] ,其作用就是将暂存区的更改提交到本地仓库

git commit有三种常用的指令格式:

  1. git commit # 提交暂存区的所有更改,并打开默认文本编辑器输入提交信息

  2. git commit -m "提交信息" # 直接附加提交信息(不打开编辑器)

  3. git commit -a -m "提交信息" # 自动将所有已跟踪文件(Tracked)的修改添加到暂存区并提交

此外,commit操作也有相应的指令进行反向操作(撤销已经提交的记录)

  1. git reset --soft HEAD~1 # 撤销上一次提交(HEAD~1),但保留所有更改在暂存区(相当于刚刚 git add 后的状态)

  2. git reset HEAD~1 # 撤销提交,并将更改移回工作区(相当于 git add 之前的状态)

  3. git revert HEAD # 生成一个新的提交,反向应用上一次提交的更改

但是注意,对于已经提交到本地仓库的commit,对它进行反向的回退操作要十分谨慎,因为这会直接影响Git记录的版本控制历史,而且后续和远程仓库的同步很容易出现冲突

再次使用git status指令查看当前状态

这个时候Git会告诉我们,工作区和本地仓库的状态已经保持一致了,没有东西需要提交,工作树当前保持干净

为了进一步查看我们Git的信息,可以使用git log指令查看我们执行commit操作之后生成的日志

这里我们可以看到上一次commit时的附带信息,比如commit ID以及作者和日期

接下来,如果我们修改file1.txt内部的内容,并尝试再进行一次提交,看看会发生什么。

使用git status查看修改文件之后的状态信息:

这里我们可以看到,Git经过对比工作区与暂存区和本地仓库发现有更改没有被提交,并且显示我们有一个modified的文件:file1.txt

同样经过添加到暂存区和提交到本地仓库的两步操作,我们可以看到具体的状态变化

git add之后的状态信息

git commit之后的状态信息

同时再使用git log查看我们第二次提交之后的日志

这时我们发现有新的commit记录了,这一次新的commit也有了一个新的独立的commit ID

3. Git的分支设计

① 简单的分支操作

我们在进行团队协作开发时,不同团队成员从某一次commit开始往后的开发会引出两条不同线,此时单线的版本管理就不能够完成整个版本控制的功能来,此时Git的分支就会开始发挥作用了

在我们前面的介绍的案例中,每一次commit都是基于前一次commit的更改之上提出的,在整个版本历史中,我们的开发过程就是一条由许多commit节点连接而成的线。每一个节点都可以找到上一级的节点,我们依靠这个设计可以方便的回溯到想要的节点状态

但是同样的,Git也能够支持我们从一个commit节点出发衍生出两个甚至更多的节点,从而生成出更多的开发线。这样设计会将整个项目的版本历史变成一棵开枝散叶的树而非单条的线

这样在团队开发时,每个成员都可以使用分支功能,从每一次版本状态出发引出一个分支,而在分支上开发不受到其他人开发的影响也不会影响到其他的开发,只需要在必要时把引申出的分支和其他分支进行合并,让双线变成单线,就可以保证主开发进程的更新推进

在开始正式的分支操作之前,我们可以来看看在前面的案例中,我们的分支信息

git branch

借助git branch指令,我们可以查看当前已有的所有分支

可以看到,在我们创建的Git仓库只给我们创建了一个默认的主分支——master分支

在前面的git log 结果中,我们也可以发现日志中也记录了分支的信息,在commit的右侧会有分支指向的信息,代表在版本历史中,某一个分支当前处于特定commit下的版本

我们可以使用指令来创建一个新的分支

git branch <分支名>          # 创建分支但不切换
git checkout -b <分支名>     # 创建并切换到新分支(旧写法)
git switch -c <分支名>       # 创建并切换到新分支(Git 2.23+ 推荐)

在记忆时,我们可以将git checkout -b <分支名> 的-b理解为branch,代表切换到分支

并可以将git switch -c <分支名> 的-c理解为create,代表创造一个分支

此外,我们使用更加频繁的指令是分支间的切换,我们可以使用git checkout <分支名>或者git switch <分支名>的方式进行切换

使用指令创建一个新分支dev之后,我们可以再使用git branch指令查看新的分支状态

此时所有分支信息中就显示了我们刚刚添加的分支dev ,同时左侧的星号标识也代表我们现在处于分支dev

使用git log查看Git日志

我们能够更加清楚地发现,两个分支此时都指向了同一个commit提交。这表示两个分支目前都基于ID号起始为b306的commit版本进行开发,此时它们可以有各自独立的工作区和暂存区内容

接下来尝试在dev分支上进行进一步的开发,并尝试对比在不同分支上开发的差异

在分支dev上,我们先建立一个新的文件file2.txt 并进行暂存和提交

此时我们可以通过更新后的git log看到:

当下我们的dev分支指向了我们新建立的ID号起始为82a4的commit版本,而主分支master分支还指向上一次commit不变

② 分支合并与不同的合并情况

那么假设现在我们在dev分支上的操作就代表我们在开发功能时操作的一种情况:从主分支引出的版本上继续开发,添加了我们自己的内容之后并提交,而且我们在dev上新增加的更改内容与master分支上的内容并不冲突(没有操作同一个文件)。此时我们尝试将在新分支上确认开发完成无误的内容合并到主分支上,以此完成协作功能的融合

合并分支的功能将由指令git merge <分支名> 来实现。这里需要注意一个逻辑上的问题:我们应该在哪个分支上进行git merge操作?为了体现并保证分支操作的权限层次,git merge操作是由纳入其他分支的分支来完成的。在某一分支上使用git merge another-branch ,将会把another-branch的内容融汇到当前分支上来,这保证了当下所在分支操作的主动性

例如,我们想要将dev分支的内容合并到主分支上时,应该先切换到主分支上,以主分支作为我们操作的主体将dev分支融入进来

而此时我们使用git switch切换到主分支时,我们会发现Git自动将我们的工作区切换到了master指向的commit版本时的工作区(这个时候没有file2.txt),从而说明Git在切换分支时,会自动的将相应的版本内容进行还原

接下来尝试合并dev分支,并使用git log查看Git日志信息

注意观察,我们能够发现Git的这一次分支合并使用了Fast-forward方式,对比前后日志信息,Git的commit提交信息没有新增,只是将当前主分支的指向移动到了dev下最新的commit版本。这种合并的方式对应的就是两分支最新commit版本间差异内容只有新增没有冲突的情况,Git会直接将分支指向移动到最新的commit版本以此来简化操作

但是实际上开发时我们会遇到的分支间内容的情况并不只有当前这种子分支只包含新增内容的情况

假如我们回到开始刚刚建立dev分支之后,此时我们不再建立一个新的file2.txt文件,而是直接在file1.txt文件中修改,在原本的内容后加一行新的文本,并以相同的方式添加到暂存区并提交到本地仓库中

通过git log我们可以观察此时的Git版本状态

而我们继续回到主分支并添加一个新的文件file2.txt,并尝试按照相同的合并分支操作在主分支执行git merge操作:

在执行merge操作之前,Git日志显示为如下

而直接执行git merge dev之后,我们会发现终端窗口并没有直接显示合并成功的信息,而是转到了一个默认的文本编辑界面(一般是vim)

界面显示的内容告诉我们,现在我们需要写一个commitcomment来说明为什么要进行这样一次合并。为什么会出现这样的情况呢?原因是我们这一次进行的分支合并操作并不是简单的子分支在主分支直接新更改文件,而是两个分支都各自产生了新的变化,出现了新的commit版本。相比于两个分支在分开的时候,各自分支都产生了新的版本,合并产生新的版本不再能够选取某一个分支的commit版本作为最新的commit版本了,需要将两个分支新加的内容互相合并在一起。

Git在面对这样的合并操作时,选择的策略是创建一个新的commit,将两个分支新增的内容汇集到一起后作为一个版本进行提交,而这样的一个commit版本和别的commit一样,也需要手动给出一个comment说明commit的内容详情。所以Git会在git merge dev执行之后自动打开一个文本编辑的窗口来需要我们手动填写一个commitcomment

而我们也可以在合并分支之后执行git log来确认这一点

在上面的介绍中,我们观察了两种不同的分支合并的案例,而还有一种我们实际经常会遇到的情况:你和其他团队成员在开发时,都要在某些基础性的组件上做一些修改,这会可能导致在同一个文件出现相同位置的修改,这时的合并就会出现问题了

对于这种情况,我们也可以尝试看看Git会怎么解决这个问题

再次假设我们回到开始刚刚建立dev分支之后,此时我们也不再建立新文件了,我们也在主分支上对file1.txt文件增加一行和dev分支上不同的行。此时在两个分支上file1.txt的第一行文本都是相同的,但是第二行文本不同,和我们预先想要观察的情况一致。同样在主分支上进行添加到暂存区并提交到本地仓库上

此时不妨尝试看看合并会出现什么效果

执行完合并指令之后,终端输出了合并的信息。此时我们能够发现,Git的输出告诉我们我们的分支合并操作遇到了冲突,这个冲突发生在file1.txt中,于是我们的自动合并分支操作失败了,目前需要我们修复冲突并再一次提交结果

此时我们输入git status查看当前状态可以发现

我们现在有尝试合并但没有合并成功的提交,没有合并的内容中存在两个分支都进行了修改的部分:file1.txt

同时观察Git给我们的提示,我们可以尝试解决冲突后再进行提交,或者直接使用指令放弃这一次合并

如何解决冲突呢?我们使用文本编辑工具查看现在的冲突文件——file1.txt

本来应该是两行文本的文件现在出现了新的结构信息

在冲突出现的部分,我们现在所在的、想要把其他分支融入进来的分支(HEAD目前所指的)原本的内容被写在了<<<<<<< HEAD=======

的中间,而要被融入进来的分支的内容则写在了=======>>>>>>> dev 中间。此时如果需要解决冲突,我们就需要手动地对比两个部分的内容,选择性的保留真正应该留下的部分,并删除掉Git自动生成的区分两个内容的符号标识

在这里,我们把文件的第二行文本修改成 merge dev to master ,保存后将我们的修改添加到暂存区之后再使用git status观察状态

此时的Git提示消息变成了:所有的冲突已经解决但是你仍旧在合并的过程中,需要使用git commit来总结这次合并

按照它的指示我们进行提交并查看提交之后的合并情况

此时同我们前一次合并情况类似,也生成了新的commit版本(除开冲突的发生确实是相同情况),我们的分支合并也顺利的完成

4. Git的远程仓库协作

在上面的内容,我们简单地了解了Git在本地使用时的设计思想和使用方法,但是使用我们上面的内容还不足以完成便捷的团队开发

这个时候就需要使用Git的远程仓库功能来进行协作开发了

理论上,利用Git分布式的特点,我们可以直接与任何一位想要协作的成员共享我们自己的本地仓库,让他使用我们的仓库创建分支进行开发

但是这样分散的管理方式和不便的信息同步并不利于我们的使用,所以通常我们会使用部署Git服务器的方式,将我们的Git仓库存储在服务器上供团队成员访问,我们只需要从服务器上拉取需要开始开发的版本并上传我们开发完成的版本就可以了

幸运的是,现在我们能够轻松使用某一些公司提供的在线Git服务器的服务,这样就能避免我们自己搭建服务器的成本开销了

在提供这样服务的公司中,Github是使用相当广泛的一个。我们可以轻松的在Github上建立远程仓库,并在上面进行管理和并使用它的其他功能进行开发。而且,作为全世界各地的远程仓库的集合,Github逐渐变成了一个代码项目的社区,我们可以访问其他程序员开放的仓库,获取他们公开的代码进行学习和参考

我们将使用Github来了解Git的远程仓库的开发功能

①远程仓库的创建与关联

在使用Github创建远程仓库之前,我们需要准备好Github的账户并准备好SSH的相关内容(Github配置ssh key的步骤(大白话+包含原理解释)_github生成ssh key-CSDN博客),相关的详细教程我们能从网络上找到许多

完成准备工作之后,我们就可以在Github的网页创建一个远程仓库:只需要在网页中选择New repository的项目,并在详细页面中按照要求填写好这个仓库的名字和说明即可

此时,我们在Github的服务器上就有一个远程仓库了,不过在你上传任何文件之前,Github服务器上存储的仓库是一个空的仓库

借助Github的网页,我们可以添加一些基本的文件,比如README.md这样的说明文档。甚至极端点来说,我们完全可以借助Github的页面在远程仓库中完成所有代码的编写(当然没有人会这么干)

在进行关联管理之前,你的本地仓库和远程仓库是分离的、独立的。我们在开发的第一件事就是把你的本地仓库和Github上的某一个远程仓库关联起来

我们需要在本地使用指令告诉我们的本地仓库:“现在我们需要关联一个远程仓库了,之后你们之间将会建立起关系”

git remote add <远程名称> <URL>  # 添加远程仓库

在这里,远程名称是你在本地开发时给远程仓库起的一个代号,它可以你想到的任何一个名字,但是我们通常会把这个名字设置为origin(换成其他的有时候不如简单通用点)

<URL>则是我们通过网络访问远程仓库的通道,Git将会通过这个通道去与Git服务器进行沟通

Github为了保证远程仓库与外界的每一次传递都是安全且透明的,会需要我们使用SSH进行通讯,这也是我们需要提前准备好SSH认证的原因

另一方面,我们也可以使用Git指令删除本地仓库与远程仓库的关联,指令如下

git remote remove <远程名称>     # 删除远程仓库

执行完指令之后,我们在本地上进行开发时就会用你选取的代号(默认使用origin)来代指你关联的那个远程仓库了

使用指令git remote -v可以查看我们现在的远程仓库信息

而同样的,假如我们想将一个其他的远程仓库搬到我们本地来,需要使用指令

git clone <远程仓库URL> [本地目录名]

这个指令相当于下载一个远程仓库到本地来,并不会直接建立起其他的Git关系

②远程仓库上的推送和拉取

那么对于一个新建立的远程仓库,我们要做的第一步就是把目前完整的本地仓库备份到远程仓库上,使用指令

git push -u origin master

这样从本地仓库传输到远程仓库,对远程仓库进行更新的操作叫做推送push

Git执行推送的的指令格式为git push <远程名称> <本地分支>:<远程分支>

但是一般会简写为git push origin feature # 推送本地 feature 分支到远程 feature 的形式

而必要时会使用参数:

  • -u / --set-upstream:首次推送时建立追踪关系,后续可简写 git push

  • --force / -f:强制推送(慎用,会覆盖远程历史)

所以在首次推送给远程仓库时,我们会使用-u来建立远程仓库的master和本地仓库的master 分支的关联

推送完成之后,我们就可以在Github的网页上观察到我们的远程仓库内容已经变成和我们本地一样了

而假设我们其他团队的成员也更新了新的内容在远程仓库上,我们需要在新一版的仓库上开发呢?

我们可以在Github网页上手动添加一个README文档,以此来模拟远程仓库被其他成员更新后的结果(添加文档会自动生成一次commit

此时,我们本地开发时就需要手动从远程仓库拉取一版最新版本的仓库到本地,同样使用指令

G

拉取远程仓库分支的操作有两种常用的:

  1. git fetch(仅下载,不合并)下载远程最新数据到本地仓库,但不修改工作区

  2. git pull(下载并合并)

此时的输出显示我们拉去导致了本地分支的更新,增加一个README.md文档。同时我们本地的工作区文件中也的确增加了一个同样的文件

除此之外,我们还可以使用git log来查看拉取之后的情况

此时我们也可以看到,不仅远程仓库上操作产生的commit被同步到了本地,同时我们也可以看到远程仓库的master分支(origin/master)的commit信息也被同步到了我们的本地

③远程仓库上的PR(Pull Request)操作

通过上面的了解,我们不难发现,Git推荐我们在进行团队开发时使用创建分支开发+合并分支集成的方式进行子功能的开发

而借助于我们的远程仓库平台,我们通常会把自己在本地开发的分支直接推送到原程仓库上交由远程仓库的管理者进行审查,在将你的分支合并到重要且稳定的关键分支之上之前经过相关成员的讨论和评判,这样的功能叫做Pull Request,即PR

PR本质上是一种代码审查和合并的协作机制,允许开发者向目标分支提议合并代码变更,并请求团队审核

这种机制在安全管理代码仓库、系统确保代码安全上具有非常重要的作用。在一些复杂场景之下,PR操作会和项目文档编写、自动化测试、代码提交规则等内容结合起来,以确保每一次新增代码的安全和可靠

当我们在本地把一个新分支推送到Github的远程仓库上之后,Github的页面会自动的弹出Pull Request的提示询问我们是否要对新的分支进行PR操作

而进入创建PR的页面中,作为新功能的开发者以及请求合并新功能到主要分支的请求者,我们需要手动填写关于这一次PR操作的描述以供其他成员阅读和审核

而创建PR完成之后,我们就可以在Github的仓库页面的“Pull Request”项目中查看到刚刚创建的PR请求。点进详情页中,作为团队开发成员的我们可以对这一次PR请求进行审核(是否同意合并)并进行评论

当管理成员通过了这一次PR的审核,这一分支的代码就可以成功地合并到主要分支之上,在远程仓库上完成了新功能到主要分支上的融合

在Github的仓库主页面上就会出现如下的说明

这展示了该仓库的最近一次PR操作的的相关说明

5. Git的指令汇总小结

操作类别

指令

说明

初始化

git init

在当前目录初始化一个新的git仓库

git clone <repo-url>

克隆一个远程仓库到本地

工作区与暂存区

git status

显示工作区和暂存区的状态

git add <file>

将文件添加到暂存区

git add .

将所有改动添加到暂存区

git rm <file>

从版本控制中删除文件

git reset

将暂存区的内容还原回工作区

提交

git commit -m "message"

提交暂存区的内容到本地仓库,并附上提交信息

git commit -am "message"

直接提交修改过的文件到本地仓库(跳过暂存步骤)

日志

git log

查看提交日志

git log --oneline

以简洁的一行格式查看提交日志

分支管理

git branch

列出所有本地分支

git branch <branch-name>

创建新分支

git checkout <branch-name>

切换到指定分支

git checkout -b <branch-name>

创建并切换到新分支

git switch -c <branch-name>

创建并切花到新分支(新版本推荐)

git merge <branch-name>

将指定分支合并到当前分支

远程操作

git remote add origin <repo-url>

添加远程仓库别名为origin

git remote -v

查看所有远程仓库及其URL

git push origin <branch-name>

推送本地分支到远程仓库

git pull origin <branch-name>

从远程仓库拉取最新代码并合并到当前分支

更多详细的内容可以参考廖雪峰老师网站提供的 Git-cheat-sheet

三、小结

通过上面的内容,我们能够了解Git的基本设计,并对其简单的使用有一定的掌握了

Git作为一个当下几乎不可规避使用的代码版本控制工具,已经成为编程中的必需品了。Git的使用在企业开发中还有更多深层次和更多元的内容,以上的内容只能当作简单的基础部分。更多有关于例如分支变基操作、git stash有关的操作等等内容还需要更多篇幅去探讨

Git在设计和迭代中也逐渐引入了许多值得我们进一步探讨的内容,比如Git版本信息存储使用的四种对象结构、映射在分支管理中的指针操作等等。这些内容与Linus使用C语言设计Git时的思想紧密相关,也同样值得我们后续分析

本文的内容只作为个人学习和整理时的总结与分享,编写中可能存在某些疏漏或者错误,如有发现进行留言或者与我联系

参考内容

文章编写参考于下面的内容,这些高价值的内容对于本文的编写和相关部分的了解十分有帮助:

  1. Git官方网站 Git --- Git

  2. 廖雪峰老师的Git教程 简介 - Git教程 - 廖雪峰的官方网站

  3. 图解Git网站 图解Git

  4. (39 封私信 / 54 条消息) Git入门教程,详解Git文件的四大状态 - 知乎

  5. 【Cheat Sheet】【04】一张图掌握Git,Github核心功能都免费开放了,你还不会用Git吗?_哔哩哔哩_bilibili