Git 的正确使用姿势与最佳实践 | 青训营

124 阅读13分钟

为什么要学习Git

  1. 协同工作:业界绝大多数公司都是基于Git进行代码管理,因此是一个程序员的必备技能。
  2. 开源社区:目前绝大多数的开源项目都是基于Git维护的,参与这些项目的开发都需要使用Git。

1. Git是什么

  1. Git是什么?

    Git 是一个免费的开源分布式版本控制系统,旨在从小型到超大型项目处理所有事情、速度和效率。

  2. 版本控制是什么?

    一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统

  3. 为什么需要版本控制?

    更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;也能的够随时切换到不同的版本,回滚误删误改的问题代码。

版本控制类型代表性工具解决的问题
本地版本控制RCS本地代码的版本控制
集中式版本控制SVN提供一个远端服务器来维护代码版本,本地不保存代码版本,解决多人协作问题
分布式版本控制Git每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题

1.1. 本地版本控制

  1. 最初的方式

    通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本

  2. 解决方案

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

  3. 基本原理

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

  4. 缺点

    RCS这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因而使用的场景非常有限,因此衍生出了集中式版本控制

1.2. 集中版本控制

代表性工具:SVN

  • 基本原理
    1. 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中
    2. 增量保存每次提交的Diff果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
  • 优点
    1. 学习简单,更容易操作
    2. 支持二进制文件,对大文件支持更友好
  • 缺点
    1. 本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交
    2. 分支上的支持不够好,对于大型项目团队合作比较困难
    3. 用户本地不保存所有版本的代码,如果服务端故障容易导致历史版本的丢失。

1.3. 分布式版本控制

代表性工具:Git

  • 基本原理
    1. 每个库都存有完整的提交历史,可以直接在本地进行代码提交
    2. 每次提交记录的都是完整的文件快照,而不是记录增量
    3. 通过Push等操作来完成和远端代码的同步
  • 优点
    1. 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
    2. 分支管理功能强大,方便团队合作,多人协同开发
    3. 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失
  • 缺点
    1. 相对SVN更复杂,学习成本更高
    2. 对于大文件的支持不是特别好(git-lfs工具可以弥补这个功能)

2. Git的基本使用方式

2.1. 配置

2.1.1. 创建仓库

Git使用git init命令来初始化一个Git仓库,Git的很多命令都需要在Git的仓库中运行,所以git init是使用Git的第一个命令。

在执行完成git init命令后,Git 仓库会生成一个.git目录,该目录包含了资源的所有元数据,其他的项目目录保持不变。

工作区、暂存区和版本库

工作区:就是你在电脑里能看到的目录。

暂存区:英文叫stage或index。一般存放在.git目录下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

2.1.2. Git Config

不同级别的Git配置

system-系统级: 在git安装以后,git的默认配置项都在这里;

global-全局级:登录用户全局级别的git配置;

local-仓库级: 对不同的仓库进行自定义配置。

每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置

常见Git配置

  1. 用户名配置

    git config --global user.name "your_username"

    git config --global user.email "your_email@example.com"

  2. Instead of配置

    git config --global url.git@github.com:.insteadOf https://github.com

  3. Git命令别名配置

    git config --global alias.cin "commit--amend --no-edit"(表示要输入 git commit--amend --no-edit时,只需要输入git cin)

2.1.3. Git Remote

除了基本配置,还有remote配置,表示本地和远程仓库的关联信息,remote一般分为http和ssh两种。

  • 查看Remote:git remote -v
  • 添加Remote:git remote add <remote_name> <remote_url>

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

了解了什么是remote配置后,那本地是如何与remote进行通信呢?

一般会通过http和ssh两种协议,这两种协议都需要对身份进行认证,对go这种语言,依赖库很多,所以我们需要不断输入认证的账号码,这肯定是件很麻烦的事情,因此需要配置一下免密的认证方式。

  • HTTP Remote 免密配置
    • 内存:git config --global credential.helper 'cache --timeout=3600'
    • 硬盘:git config --glabal credential helper 'store --file/path/to/credential-file"不指定目录的情况默认是~/.git-credentials
    • 将密钥信息存在指定文件中,具体格式${scheme}://${user}:${password}@github.com
  • SSH Remote 免密配置
    • SSH可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问
    • 目前的Key的类型四种,分别是dsa、rsa、ecdsa、ed25519。默认使用的是rsa,由于一些安全问题,现在已经不推荐使用dsa和rsa了,优先推荐使用ed25519
    • ssh-keygen -t ed25519 -C"your_emaile@example.com"密钥默认存在~/.ssh/id_ed25519.pub

2.2. 提交与修改

git add添加文件到暂存区。

git commit提交暂存区到本地仓库。

git status查看仓库当前的状态,显示有变更的文件。

git diff比较文件的不同,即暂存区和工作区的差异。

git reset回退版本。

git rm将文件从暂存区和工作区中删除。

git mv移动或重命名工作区文件。

git commit -amend 修改最近的一次commit信息,修改之后commit id会变

git rebase-i HEAD~3实现对最近三个commit的修改:合并commit;修改具体的commit message;删除某个commit

git filter -branch指定删除所有提交中的某个文件或者全局修改邮箱地址等操作

2.3. 远端同步

2.3.1. 拉取代码

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

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

git pull拉取远端某分支,并和本地代码进行合并,操作等同于git fetch+git merge也可以通过git pull -rebase完成git fetch+git rebase操作。

可能存在冲突需要解决。

2.3.2. 推送代码

一般使用git push <remote><branch>命令即可完成

冲突问题

  1. 如果本地的commit记录和远端的commit历史不一致,则会产生冲突,比如git commit --amend or git rebase都有可能导致这个问题。
  2. 如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过git push <remote><branch> -f来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。

推送规则限制

可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。

2.4. 常见问题

  • 明明配置了Git配置,但是依然没有办法拉取代码?
    1. 免密认证没有配。
    2. Instead Of配置没有配,配的SSH免密配置,但是使用的还是HTTP协议访问。
  • 为什么Fetch了远端分支,但是本地当前的分支历史还是没有变化?

    Fetch会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。

3. Git研发流程

3.1. 不同的工作流

类型代表平台特点合入方式
集中式工作流Gerrit/SVN只依托于主干分支进行开发,不存在其他分支Fast-forward
分支管理工作流Github/Gitlab可以定义不同特性的开发分支,上线分支,在开发分支完成开发后再通过MR/PR合入主干分支自定义。Fast-forward or Three-way Merge都可以

3.2. 集中式工作流

3.2.1. 概述

只依托于master分支进行研发活动

3.2.2. 工作方式

  1. 获取远端master代码
  2. 直接在master分支完成修改
  3. 提交前拉取最新的master代码和本地代码进行合并(使用rebase),如果有冲突需要解决冲突
  4. 提交本地代码到master

3.2.3. Gerrit

Gerrit是由Google开发的一款代码托管平台,主要的特点就是能够很好的进行代码评审。在aosp(android open source project)中使用的很广,Gerrit的开发流程就是一种集中式工作流。

  • 基本原理
    1. 依托于Change ID概念,每个提交生成一个单独的代码评审。
    2. 提交上去的代码不会存储在真正的refs/heads下的分支中,而足存在一个refs/for的引用下。
    3. 通过refs/meta/config下的文件存储代码的配置,包括权限,评审等配置,每个Change都必须要完成Review后才能合入。
  • 优点
    1. 提供强制的代码评审机制,保证代码的质量
    2. 提供更丰富的权限功能,可以针对分支做细粒度的权限管控
    3. 保证master的历史整洁性
    4. Aosp多仓的场景支持更好
  • 缺点
    1. 开发人员较多的情况下,更容易出现中突
    2. 对于多分支的支持较差,想要区分多个版本的线上代码时,更容易出现问题
    3. 一般只有管理员才能创建仓库,比较难以在项目之间形成代码复用,比如类似的fork操作就不支持。

3.3. 分支管理工作流

分支管理工作流特点
Git Flow分支类型丰富,规范严格
GitHub Flow只有主干分支和开发分支,规则简单
Gitlab Flow在主干分支和开发分支之上构建环境分支、版本分支,满足不同发布or环境的需要

3.3.1. Git Flow

Git Flow是比较早期出现分支管理策略

  • 包含五种类型的分支
    1. Master:主干分支
    2. Develop:开发分支
    3. Feature:特性分支
    4. Release:发布分支
    5. Hotfix:热修复分支
  • 优点 如果能按照定义的标准严格执行,代码会很清晰,并且很难会出现混乱。
  • 缺点 流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。

3.3.2. Gitub Flow

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

  • 选择团队合作的方式
    1. owner创建好仓库后,其他用户通过Fok的方式来创建自己的仓库,并在fork的仓库上进行开发
    2. owner创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
  • 创建一个Pull Request
    1. 创建一个main主分支
    2. 创建一个feature分支
    3. 创建一个feature到main的Pull Request
  • 可以在Pull Request页面执行CI/CA/CR等操作,等检查通过后,执行合入
  • 可以通过进行一些保护分支设置,来限制合入的策略,以及限制直接的push操作

3.3.3. Gitlab Flow

Gitb推荐的工作流是在GitFlow和Github Flow上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境。

原则:upstream first上游优先

只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master

3.4. 代码合并

  1. Fast-Forward:不会产生一个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新source branch后才可以合入。
  2. Three-Way Merge:三方合并,会产生一个新的merge节点

3.5. 如何选择合适的工作流

选择原则:没有最好的,只有最合适的

针对小型团队合作,推荐使用Github工作流即可

  1. 尽量保证少量多次,最好不要一次性提交上干行代码
  2. 提交Pull Request后最少需要保证有CR后再合入
  3. 主干分支尽量保持整洁,使用fast-forward合入方式,合入前进行rebase

大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中。

3.6. 常见问题

  1. 在Gerrit平台上使用Merge的方式合入代码。

    Gerrit是集中式工作流,不推荐使用Merge方式合入代码,应该是在主干分支开发后,直接Push。

  2. 不了解保护分支,Code Review,CI等概念,研发流程不规范。

    保护分支:防止用户直接向主干分支提交代码,必须通过PR来进行合入。

    Code Review,CI:都是在合入前的检查策略,Code Review是人工进行检查,CI则是通过一些定制化的脚本来进行一些校验。

  3. 代码历史混乱,代码合并方式不清晰。

    不理解Fast Forward和Three Way Merge的区别,本地代码更新频繁的使用Three Way的方式,导致生成过多的Merge节点,使提交历史变得复杂不清晰。

    如有错误,敬请批评指正。