Git基础

Git基础

基础概念

Git是一种开源的分布式版本控制工具,可以有效、高速地处理项目版本管理。Git的整体工作流程可以表示为下图:

图中分别对应到不同的命令,完成不同的功能。

  1. clone:从远程仓库中克隆代码到本地仓库
  2. checkout:切换到本地仓库的某个分支,进行工作
  3. add:将代码提交到暂存区
  4. commit:将暂存区中的内容提交到本地仓库
  5. pull:从远程仓库中抓取内容到本地仓库,并进行合并
  6. push:将本地仓库中的代码推送到远程仓库

基本命令

仓库初始化

首先需要完成Git的下载,Git的下载地址为:Git - Downloads (git-scm.com)。在安装完成之后,需要设置用户名称和email地址,这也是每次Git提交使用到的用户信息:

1
2
git config --global user.name "xxx"
git config --global user.email "xxx"

这两条命令完成的是全局配置的修改,全局配置对应到用户目录下的.gitconfig文件。

接下来可以在一个新路径下创建仓库repository,使用初始化命令:

1
git init

创建成功后,会在路径下看到隐藏文件夹.git

本地仓库操作

在Git中,存在仓库,暂存区以及工作区的概念。

Git工作目录下,对于文件的修改都是在工作区中完成的。修改(增删改)后的文件处于工作区,需要先使用add命令将其添加到暂存区中。暂存区相当于提交到仓库之前的缓冲区,其中的内容需要使用commit命令添加到仓库中,此时才算完成了一次提交。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看修改状态(暂存区+工作区)
git status

# 添加文件到暂存区
git add 文件名|通配符
eg: git add .

# 提交暂存到本地仓库
git commit -m "一些注释message"

# 查看提交日志
git log [option]
--all: 显示所有分支
--pretty=oneline: 将提交信息显示为一行
--abbrev-commit: 使得输出的commitId更加简短
--graph: 以图的形式显示

# 查看到已经删除的提交记录
git reflog

# 版本回退
git reset --hard <commitId>

一些时候,我们会有一些文件不需要纳入Git的管理,例如一些日志文件,临时文件等。我们可以在工作目录下创建一个名为.gitignore的文件,在其中列出需要忽略的文件模式。

分支

几乎所有的版本控制系统都以某种形式支持了分支。通过使用分支,可以将工作从开发主线上分离开来,进行Bug修改,新功能开发等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看本地分支
git branch

# 创建本地分支
git branch 分支名称

# 切换当前分支
git checkout 分支名称
git checkout -b 分支名称: 切换到一个不存在的分支(创建并切换)

# 合并内容到当前分支
git merge 分支名称

# 删除分支
# 不能删除当前分支,只能删除其他分支
git branch -d 分支名称: 删除前进行检查
git branch -D 分支名称: 强制删除

在进行分支合并的时候,可能会出现分支之间的冲突情况,此时是无法完成分支合并的。需要我们手动在本地完成冲突解决之后,才能进行合并。冲突解决分为三步,首先处理文件中冲突的地方,即决定最终文件内容是什么,之后将解决完冲突的文件加入暂存区(add),最后提交到仓库(commit)。

远程仓库操作

操作远程仓库有两种方式,一种是先初始化本地库,然后与已创建的远程库进行对接。另一种是直接从远程仓库克隆代码到本地,直接建立联系。

我们可以通过命令查看远程仓库:

1
git remote

与远程仓库进行对接的命令为:

1
2
3
git remote add <远端名称> <仓库名称>
- 远端名称,默认是origin,取决于远端服务器设置
- 仓库路径,需要从远端服务器获取

增加远程仓库之后,我们仍然是先在本地完成相关操作,提交到本地仓库之后,再进行本地仓库和远程仓库之间的操作,主要包括从远程仓库拉取以及推送到远程仓库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 推送到远程仓库
git push [-f] [--set-upstream] [远端名称 [本地分支名][:远端分支名]]
- 如果远端分支名和本地分支相同,则可以只写本地分支
- -f表示强制覆盖
- --set-upstream表示推送到远端的同时建立起与远端分支的关联关系
- 如果当前分支已经和远端分支关联,则可以直接git push

# 查看远端分支与本地分支的关联关系
git branch -vv

# 克隆远程仓库到本地
git clone <远程仓库路径> [本地目录]

# 拉取远程仓库内容
# 抓取:将远程仓库中的更新都抓取到本地,但是不进行合并
git fetch [remote name] [branch name]
- 不指定远端名称和分支名,则抓取所有分支
# 拉取:将远程仓库的修改拉到本地并自动进行合并,等同于fetch+merge
git pull [remote name][branch name]

如果在同一段时间内,不同用户修改了同一个文件上同一行的内容,此时向远程仓库推送的时候会产生合并冲突。此时需要先拉取远程仓库现在的提交,经过合并,在本地解决冲突之后才能推送到远端分支上。推送操作相当于本地分支的内容合并到远程分支上,也需要解决冲突。

Git workflow

参与远端项目

首先明确一个工作环境,目前我们存在Remote远程仓库,Local本地Git以及Risk本地磁盘:

  1. 首先使用git clone,将远程仓库中的内容克隆到本地
  2. 使用git checkout -b xxx,切换到一个我们新的feature分支上
  3. 进行代码修改,本地磁盘中的代码发生改变
  4. 修改完成之后,可以使用git diff来查看本地磁盘上的代码与本地Git中的区别
  5. 需要将本地磁盘中的代码与本地Git进行同步,则先使用git add添加到暂存区,然后使用git commit提交到本地仓库
  6. 下一步需要将本地Git中的内容同步到远程仓库中,则使用git push origin xxx命令

一种常见的情况是,我们在修改代码的过程中,发现远端仓库上main代码发生了变化,我们想要让自己的代码修改在变化后的远端代码上仍然可行,于是需要下面的步骤:

  1. 使用git checkout main切换回main分支
  2. 将变化后的远端代码同步到本地,使用git pull origin main
  3. 然后回到我们自己的feature分支上,git checkout xxx
  4. 使用git rebase main命令。这表示在当前分支上,先更新变化的main,然后根据当前分支上的commit进行内容修改,中途可能会出现代码冲突,需要手动处理
  5. 之后将合并且更新过的xxx分支代码提交到远端仓库上

至此,在远端仓库上会存在一个我们自己的feature分支xxx。

  1. 我们希望这个分支能够合并到项目的main分支中,则需要发起pull request请求
  2. 之后,项目的负责人决定是否合并,如果决定合并,则使用squash and merge。这表示将xxx分支中的内容合并到main分支中,但是只进行一次commit,这个commit中是xxx中的所有变化。squash完成的是commit数量和结构的修改。如果直接merge,则在main中会一次出现很多的commit。
  3. 我们的xxx分支修改内容被合并到了main分支中,就可以在远端仓库中删除对应xxx分支
  4. 之后在本地,使用git branch -d xxx,删除本地的Git 分支
  5. 最后再使用git pull origin main,拉取最新合并后的远端代码

于是一次代码修改提交以及就此完成,我们得到了更新后的最新代码,在此基础上重复该流程,进行下一个功能的开发。

分支管理

在进行企业级项目开发的时候,通常需要多人的协作开发,此时需要遵循一定的分支管理模式。GitFlow是其中一种分支管理模式。在GitFlow上有几个生命周期较长的重要分支:

  • main:main分支,发布分支,其中的commit记录应该是每次发布或者hot fix
  • dev:开发分支,记录了所有的开发主线,在每次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
2
3
git clone <主仓库地址>
cd <仓库目录>
git submodule update --init --recursive

添加子模块

添加新的子模块:

1
git submodule add ***.git 目录名

更新子模块

1
2
3
4
5
# 更新全部的子模块
git submodule update --recursive --remote

# 更新某个子模块
git submodule update --remote 目录名

删除子模块

1
2
3
4
5
6
7
8
9
10
# 删除子模块目录及源码
rm -rf 子模块目录
# 删除项目目录下.gitmodules文件中子模块相关条目
vim .gitmodules
# 删除配置项中子模块相关目录
# 删除模块下的子模块目录,每个子模块对应一个目录,注意只删除对应的子模块目录即可
vim .git/config
rm .git/module/子模块名

git rm --cached 子模块名称

需要注意的是,每次对子模块的修改,反应到主目录中都会对应一个commit信息的修改,因此如果我们修改了子模块,除了提交子模块的修改,还需要提交主目录中对应子模块的commit变化。

常用解决方案

在Mac中忽略.DS_Store文件

在Mac下的每个目录中,都会有一个.DS_Store文件。该文件是用来给Mac存储文件夹的显示信息的,其中包含了一些为系统所需要的元数据。但是这个文件通常不是一个项目必备的,也就不需要将其提交到Git中。在Git中忽略某些文件可以使用.gitignore,我们也可以通过在该文件中添加如下内容表示忽略.DS_Store文件,其中**/表示匹配所有的文件夹

1
**/.DS_Store

其他的相关匹配含义:

  • #:表示注释
  • /:匹配项目的根路径
  • *:匹配任意个字符,但是不会匹配路径分隔符/
  • ?:匹配一个任意字符,但是不会匹配路径分隔符/
  • **/:匹配所有的文件夹
  • /**:匹配文件夹的所有内容

每个项目都有一个.gitignore,在每个项目中都这样配置当然是可行的。但是这样的确过于麻烦。我们可以将其配置到全局中。首先可以通过git config --list来查看目前的全局配置,实际上这个配置对应的就是~/.gitconfig文件中的内容。

为了完成全局配置,我们需要首先创建一个文件~/.gitignore_global,然后在其中添加和上面相同的内容,然后对Git进行全局设置,让其忽略.gitignore_global中的所有文件:

1
git config --global core.excludesfile ~/.gitignore_global

这样就将配置设置到了全局,而不需要每个项目都设置一遍了。

忽略文件权限变更

在默认情况下,文件权限的变更也会被记录在Git中,不过我们可以通过如下设置进行忽略:

1
2
3
git config core.filemode false

git submodule foreach --recursive git config core.filemode false # 每个子模块都忽略

参考资料

  1. 十分钟学会正确的github工作流,和开源作者们使用同一套流程_哔哩哔哩_bilibili
  2. GitHub Documentation
  3. Mac中Git忽略.DS_Store文件 | ZHENG Zi'ou (orianna-zzo.github.io)
  4. gitflow-workflow
  5. trunk-based-development
  6. git -- 子模块的配置与使用配置完子模块后,可以在一个大项目中管理很多嵌套的git子项目,当然也可以管理非git子模 - 掘金

Git基础
http://example.com/2023/02/04/Git基础/
作者
EverNorif
发布于
2023年2月4日
许可协议