Git基础
Git基础
基础概念
Git是一种开源的分布式版本控制工具,可以有效、高速地处理项目版本管理。Git的整体工作流程可以表示为下图:
图中分别对应到不同的命令,完成不同的功能。
- clone:从远程仓库中克隆代码到本地仓库
- checkout:切换到本地仓库的某个分支,进行工作
- add:将代码提交到暂存区
- commit:将暂存区中的内容提交到本地仓库
- pull:从远程仓库中抓取内容到本地仓库,并进行合并
- push:将本地仓库中的代码推送到远程仓库
基本命令
仓库初始化
首先需要完成Git的下载,Git的下载地址为:Git - Downloads (git-scm.com)。在安装完成之后,需要设置用户名称和email地址,这也是每次Git提交使用到的用户信息:
1 |
|
这两条命令完成的是全局配置的修改,全局配置对应到用户目录下的.gitconfig
文件。
接下来可以在一个新路径下创建仓库repository,使用初始化命令:
1 |
|
创建成功后,会在路径下看到隐藏文件夹.git
。
本地仓库操作
在Git中,存在仓库,暂存区以及工作区的概念。
Git工作目录下,对于文件的修改都是在工作区中完成的。修改(增删改)后的文件处于工作区,需要先使用add命令将其添加到暂存区中。暂存区相当于提交到仓库之前的缓冲区,其中的内容需要使用commit命令添加到仓库中,此时才算完成了一次提交。
1 |
|
一些时候,我们会有一些文件不需要纳入Git的管理,例如一些日志文件,临时文件等。我们可以在工作目录下创建一个名为.gitignore
的文件,在其中列出需要忽略的文件模式。
分支
几乎所有的版本控制系统都以某种形式支持了分支。通过使用分支,可以将工作从开发主线上分离开来,进行Bug修改,新功能开发等。
1 |
|
在进行分支合并的时候,可能会出现分支之间的冲突情况,此时是无法完成分支合并的。需要我们手动在本地完成冲突解决之后,才能进行合并。冲突解决分为三步,首先处理文件中冲突的地方,即决定最终文件内容是什么,之后将解决完冲突的文件加入暂存区(add),最后提交到仓库(commit)。
远程仓库操作
操作远程仓库有两种方式,一种是先初始化本地库,然后与已创建的远程库进行对接。另一种是直接从远程仓库克隆代码到本地,直接建立联系。
我们可以通过命令查看远程仓库:
1 |
|
与远程仓库进行对接的命令为:
1 |
|
增加远程仓库之后,我们仍然是先在本地完成相关操作,提交到本地仓库之后,再进行本地仓库和远程仓库之间的操作,主要包括从远程仓库拉取以及推送到远程仓库。
1 |
|
如果在同一段时间内,不同用户修改了同一个文件上同一行的内容,此时向远程仓库推送的时候会产生合并冲突。此时需要先拉取远程仓库现在的提交,经过合并,在本地解决冲突之后才能推送到远端分支上。推送操作相当于本地分支的内容合并到远程分支上,也需要解决冲突。
Git workflow
参与远端项目
首先明确一个工作环境,目前我们存在Remote远程仓库,Local本地Git以及Risk本地磁盘:
- 首先使用
git clone
,将远程仓库中的内容克隆到本地 - 使用
git checkout -b xxx
,切换到一个我们新的feature分支上 - 进行代码修改,本地磁盘中的代码发生改变
- 修改完成之后,可以使用
git diff
来查看本地磁盘上的代码与本地Git中的区别 - 需要将本地磁盘中的代码与本地Git进行同步,则先使用
git add
添加到暂存区,然后使用git commit
提交到本地仓库 - 下一步需要将本地Git中的内容同步到远程仓库中,则使用
git push origin xxx
命令
一种常见的情况是,我们在修改代码的过程中,发现远端仓库上main代码发生了变化,我们想要让自己的代码修改在变化后的远端代码上仍然可行,于是需要下面的步骤:
- 使用
git checkout main
切换回main分支 - 将变化后的远端代码同步到本地,使用
git pull origin main
- 然后回到我们自己的feature分支上,
git checkout xxx
- 使用
git rebase main
命令。这表示在当前分支上,先更新变化的main,然后根据当前分支上的commit进行内容修改,中途可能会出现代码冲突,需要手动处理 - 之后将合并且更新过的xxx分支代码提交到远端仓库上
至此,在远端仓库上会存在一个我们自己的feature分支xxx。
- 我们希望这个分支能够合并到项目的main分支中,则需要发起
pull request
请求 - 之后,项目的负责人决定是否合并,如果决定合并,则使用
squash and merge
。这表示将xxx分支中的内容合并到main分支中,但是只进行一次commit,这个commit中是xxx中的所有变化。squash完成的是commit数量和结构的修改。如果直接merge,则在main中会一次出现很多的commit。 - 我们的xxx分支修改内容被合并到了main分支中,就可以在远端仓库中删除对应xxx分支
- 之后在本地,使用
git branch -d xxx
,删除本地的Git 分支 - 最后再使用
git pull origin main
,拉取最新合并后的远端代码
于是一次代码修改提交以及就此完成,我们得到了更新后的最新代码,在此基础上重复该流程,进行下一个功能的开发。
分支管理
在进行企业级项目开发的时候,通常需要多人的协作开发,此时需要遵循一定的分支管理模式。GitFlow是其中一种分支管理模式。在GitFlow上有几个生命周期较长的重要分支:
main
:main分支,发布分支,其中的commit记录应该是每次发布或者hot fixdev
:开发分支,记录了所有的开发主线,在每次release之前,应当将dev中的内容合并到main当中feaute
:功能分支,每个功能分支都应该从开发分支中生成,开发完成之后合并到开发分支当中
当然还有一些其他的分支,例如hot-fix,release等。在GitFlow分支管理模式中,对于分支的合并有较为严格的要求。在功能开发的时候,需要从开发分支中生成Feature分支,开发完成之后合并到dev分支当中。main分支的合并权限应当严格控制,只有在进行release的时候才能进行merge。在这种模式下,可以控制发布版本的错误,但是功能的开发到发布会经过较长的时间,因为要经过相对较长的分支合并过程。
Trunk Based Development是另外一种分支管理的方式。这种分支管理将main分支看作开发分支和发布分支,具体来说,每次进行功能开发,都是从main分支上生成的,开发完成之后Feature也都直接合并到main分支当中。每次发布也都是发布main分支。这种分支管理方式的风格更加快速,适合持续交付和持续集成。由于发布的是main分支,因此需要始终保证main分支能够随时release,这就要求Feature开发的时候,在每次merge之前都能够保证这次的merge能够release。这种保证通常是通过自动测试和构建工具提供的,在提出Pull Request之后,就能自动对代码进行测试和构建,提供一份测试报告,包括是否通过,覆盖率等。这样快速的开发方式也能=能够保证每次进行代码review的时候不需要大规模的进行。不过核心点也是需要保证main分支上的代码能够随时release。
Git子模块
通常,如果一个项目的运行需要依赖其他的项目,我们会用gitsubmodule来组织这种关系。在配置的Git子模块后,在Github中表现出来就是一个链接,这个链接会对应到一个仓库以及对应的HEAD。
默认git submodule update并不会将submodule切到任何branch,所以,默认下submodule的HEAD是处于游离状态的(detached HEAD state)。所以在修改前,记得一定要用git checkout master将当前的submodule分支切换到master,然后才能做修改和提交。
gitsubmodule操作
初始化子模块
如果第一次clone包含子模块的仓库,首先需要初始化并更新子模块:
1 |
|
添加子模块
添加新的子模块:
1 |
|
更新子模块
1 |
|
删除子模块
1 |
|
需要注意的是,每次对子模块的修改,反应到主目录中都会对应一个commit信息的修改,因此如果我们修改了子模块,除了提交子模块的修改,还需要提交主目录中对应子模块的commit变化。
常用解决方案
在Mac中忽略.DS_Store文件
在Mac下的每个目录中,都会有一个.DS_Store
文件。该文件是用来给Mac存储文件夹的显示信息的,其中包含了一些为系统所需要的元数据。但是这个文件通常不是一个项目必备的,也就不需要将其提交到Git中。在Git中忽略某些文件可以使用.gitignore
,我们也可以通过在该文件中添加如下内容表示忽略.DS_Store文件,其中**/
表示匹配所有的文件夹
1 |
|
其他的相关匹配含义:
#
:表示注释/
:匹配项目的根路径*
:匹配任意个字符,但是不会匹配路径分隔符/
?
:匹配一个任意字符,但是不会匹配路径分隔符/
**/
:匹配所有的文件夹/**
:匹配文件夹的所有内容
每个项目都有一个.gitignore
,在每个项目中都这样配置当然是可行的。但是这样的确过于麻烦。我们可以将其配置到全局中。首先可以通过git config --list
来查看目前的全局配置,实际上这个配置对应的就是~/.gitconfig
文件中的内容。
为了完成全局配置,我们需要首先创建一个文件~/.gitignore_global
,然后在其中添加和上面相同的内容,然后对Git进行全局设置,让其忽略.gitignore_global
中的所有文件:
1 |
|
这样就将配置设置到了全局,而不需要每个项目都设置一遍了。
忽略文件权限变更
在默认情况下,文件权限的变更也会被记录在Git中,不过我们可以通过如下设置进行忽略:
1 |
|