Git的最佳使用姿势与最佳实践 | 青训营笔记

168 阅读10分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。

Git基本操作

引言

为什么要学习 Git

协同开发

业界绝大多数公司都是基于 Git 进行代码管理,因此 Git 是一个程序员的必备技能。

开源社区

目前绝大多数的开源项目都是基于 Git 维护的,参与这些项目的开发都需要使用 Git 。

目标

  1. 学习基本的 Git 命令,并且了解原理,在遇到 Git 相关问题时,能自行排查并解决。
  2. 了解研发流程中的基本概念和规范,学会正确的使用 Git 。

目录

1. Git 是什么

  • 介绍版本控制的发展历史,为什么会出现 Git 。
  • 介绍 Git 的发展历史

2. Git 基本使用方法

  • Git 的基本命令介绍,如何使用这些命令,以及命令的原理。

3. Git 研发流程

  • 依托代码管理平台 Gitlab / Github / Gerrit 介绍我们如何进行代码的开发和团队协作。

Git 是什么

1. 版本控制

  • 版本控制是什么? 一种记录一个或若干文件内容的变化,以便将来查阅特定版本修订情况的系统
  • 为什么需要版本控制? 更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;也能够随时切换到不同的版本,回滚误删误改的问题代码。

版本控制类型

  • 本地版本控制 :RCS
  • 集中式的版本控制 :SVN
  • 分布式的版本控制 :Git

1.1 本地版本控制

最初的方式,通过复制本地文件夹,来完成版本控制,通过不同的文件名来区分版本。

开发了一些本地的版本控制软件,其中最流行的就是RCS

本地保存的所有变更的补丁集,可以理解成就是所有的 Diff ,通过这些补丁,我们可以计算出每个版本的实际的文件内容

缺点 :只能在本地使用,无法进行团队协作做。

1.2 集中式版本控制

代表工具 :SVN

基本原理 :

  1. 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中。
  2. 增量保存每次提交的 Diff ,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突。

优点 :

  1. 学习简单,容易操作
  2. 支持二进制文件,对大文件支持更加友好。现在一些美术团队、游戏团队可能会使用。

缺点 :

  1. 本地不储存版本管理的概念,所有提交都只能连上服务器后才可以提交。
  2. 分支上的支持不够好,对于大型项目团队合作比较困难。
  3. 用户本地不保存所有版本的代码,如果服务器端故障容易导致历史版本的丢失。

1.3 分布式版本控制

代表工具 :Git 基本原理 :

  1. 每个库都存有完整的提交历史,可以直接在本地进行代码提交。
  2. 每次提交记录的都是完整的快照,而不是记录增量。
  3. 通过 Push 等操作来完成和远端代码的同步。

优点 :

  1. 分布式开发,每个库都是一个完整的提交历史,支持本地提交,强调个体。对于开源来说非常好。
  2. 分支管理功能强大,方便团队合作,多人协同开发。
  3. 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失。

缺点 :

  1. 复杂,学习成本高。
  2. 对于大文件支持不好,一般不要在 Git 提交大文件 ( git-lfs 工具可以弥补这个功能)。

2. Git 发展历史

Github :全球最大的代码托管平台。

Gitlab :全球最大的开源代码托管平台。

Gerrit :由谷歌开发的代码托管平台,对安卓这种多仓的项目支持比较好。

Git 基本使用方法

Git 基本命令

  • 配置 :git config 、 git remote

  • 提交代码 :git add 、git commit

  • 远端同步 :push 、clone 、pull 、fetch

常见问题

  1. 为什么配置了 Git 配置,依然没办法拉取代码?
  • 没有配置密钥。
  • Instead Of 配置没有配,配的 SSH 免密配置,但是使用的还是 HTTP 协议访问。
  1. 为什么 Fetch 了远端分支,但是看本地当前的分支历史还是没有变化?
  • Fetch 会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。

1. Git 目录介绍

项目初始化

git init 默认是创建 master 分支

--initial-branch 初始化的分支

--bare 创建一个裸仓库。一般存在服务器上的仓库是这样创建的,本地的就是不加参数的创建

--template 可以通过模板来创建预先构建好的自定义 git 目录

1.1 Git Config

不同级别的配置 :global 、 system 、local。

低级覆盖高级

1.2 常见配置

用户名配置

Instead of 配置

Git 命令别名配置

2. Git Remote

查看 :git remote -v

添加 :

  • git remote add origin_ssh git@github.com:git/git.git
  • git remote add origin_http https:github.com/git/git.git

同一个 Origin 设置不同的 Push 和 Fetch URL

  • git remote set-url --add --push origin git@github.com:mymymym/git.git

image.png

2.1 HTTP Remote

免密配置

内存 :git config --global credential.helper 'cache --timeout=3600'

硬盘 :git config --global credential.helper "store --file /path/to/credential-file"

指定文件,具体格式 :${scheme}://${user}:${password}@github.com

HTTP 不推荐。没那么安全,而且不方便。

2.2 SSH Remote

免密配置:

默认使用的是rsa , 但是现在已经不推荐使用,优先使用 ed25519。

配置密钥(将其中的 rsa 换成 ed25519)

3. Git Add

创建文件,add之后放入暂存区

image.png

4. Git Commit

Commit 提交。

git cat-file -p 可以查看加密的文件内容。

image.png

5. Objects

三种 Objects

Blob :存储文件内容

Tre :存储文件目录信息

Commit :存储提交信息,一个 Commit 可以对于唯一版本的代码

三种信息串联在一起 :

  1. 通过 Commit 寻找 Tree ,每个 Commit 都会存储对应的 Tree ID

image.png

  1. 通过 Tree 存储的信息,获取对应的目录树的信息

image.png

  1. 从 Tree 中获取 Blob ID,通过 Blob ID 获取对应的文件内容

image.png

6. Refs

Refs 文件存储内容

image.png

refs 的内容就是 对应的 Commit ID

不同种类的 ref

heads 前缀表示的是分支、tags 前缀表示的是标签。

Branch :git checkout -b 可以创建一个新分支,分支一般用于开发阶段,是可以不断添加 Commit 进行迭代的

Tag :标签一般表示的是一个稳定的版本,指向的 Commit 一般不会变更

通过 git tag 命令生成 tag

image.png

7. Annotation Tag

附注标签 :一种特殊的 Tag,可以给 Tag 提供一些额外的信息。

通过 git tag -a 命令来创建。

如下,里面存储的内容和 tag 不一样了。将其打开可以看到里面的内容:

object 指向的是和之前一样的 Commit ,最后的是我们添加的内容。

image.png

8. 追溯历史版本

  • 获取当前版本代码 :通过 Ref 指向的 Commit 可以获取

  • 获取历史版本代码 :Commit 里面会存有 parent commit 字段,通过 Commit 串联获取历史版本代码

提交新版本会新增三个 Objects tree / blob / commit。ref指向新的commit。

image.png

9. 修改历史版本

  1. commit --amend :修改最近一次 commit 信息,修改之后 commit id 会变

  2. rebase :

    通过 git rebase -i HEAD~3 可以实现对最近三个 commit 的修改:

     1. 合并 commit
     2. 修改具体的 commit message
     3. 删除某个 commit
    
  3. filter --branch :指定删除所有提交中的某个文件或者全局修改邮箱地址等操作

通过 git commit --amend命令,尝试修改一下 commit message

image.png

通过 git log 查看 commit 已经变了。

10. Objects

修改 Commit 之后,老的 object 没有被删除。

悬空的 Object :没有 ref 指向的 Object

git fsck --lost-found 找出悬空 Object :

image.png

11. Git GC

通过 git gc 命令,可以删除一些不需要的 Object 以及会对 object 进行一些打包压缩来减少仓库的体积。

reflog 用于记录操作日志,防止误操作后数据丢失,通过 reflog 来找到丢失的数据,手动将日志设置为过期来删除不需要的 Object

git gc prune=now 指定的是修剪多久之前的对象,默认是两周前。

如下图,操作之后发现之前找出来的悬空 Object 已经不存在了。以及还将之前的 objects 和 refs 打包了。

image.png

image.pngimage.png

12. 完整的 Git 视图

13. Git Clone & Pull & Fetch

Clone :拉取完整的仓库到本地目录,可以指定分支,深度。

Fetch :将远端某些分支最新代码拉取到本地,不会执行 merge 操作,会修改 refs / remote 内的分支信息,如果需要和本地代码合并需要手动操作。

Pull :拉取远端某分支,并且和本地代码进行合并,操作等同于 git fetch + git merge,也可以通过 git pull --rebase 完成 git fetch + git rebase 操作。可能存在冲突,需要解决冲突。

一般对代码很清楚很了解,推荐 Pull。如果不清楚远端是什么状态会不会存在冲突,用 Fetch 。

14. Git Push

  1. 常见命令: 一般使用 git push origin master 命令即可完成
  2. 冲突问题:
    1. 如果本地的 commit 记录和远端不一样,就会产生冲突,git comit --amend or git rebase都有可能导致这个问题。
    2. 如果该分支就自己一个人使用,或者团队确认过可以修改历史则可以通过 git push origin master -f 来强制完成推送,一般不推荐在主干分支进行该操作,正常都是在解决冲突后再进行推送。
  3. 推送规则限制: 可以通过保护分支,来配置一些规则,防止误操作,或者是一些不合规的操作出现,导致代码丢失。

Git 研发流程

在 GitHub 上怎么进行团队协作。

常见问题

  1. 在 Gerrit 平台上使用 Merge 的方式合入代码
  2. 不了解保护分支, Code Review ,CI 等概念,研发流程不规范
  3. 代码历史混乱,代码合并方式不清晰

1. 不同的工作流

集中式工作流 :Gerrit 、 SVN

分支管理工作流 :Github 、 Gitlab

2. Git Flow

  • 包含五种分支:

    • Master:主干分支
    • Develop:开发分支
    • Feature:特性分支
    • Release:发布分支
    • Hotfix:热修复分支
  • 优点: 如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱。

  • 缺点: 流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。

3. Github Flow

Github 的工作流,只有一个主管那分支,基于 Pull Request 往主干分支中提交代码。

团队合作方式:

  1. owner 创建好仓库后,其他用户通过 Fork 的方式来创建自己的仓库,并且在 fork 的仓库是进行开发
  2. owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发

第二种演示

创建一个 Pull Request

  1. 创建 main 分支 :GitHub 上可以看到

image.png

  1. 创建 feature 分支 :GitHub上提示创建了新的分支

image.png

  1. 检查之后合入 main 分支

可以添加一些规则

4. Gitlab Flow

upstream first 上游优先

5. 代码合并

Fast-Forward :不会产生 merge 结点,如下:与之前对比没有 Merge 结点

image.png

Three-Way Merge :三方合入,会产生 merge 结点

image.png

end !