0%

Git的正确使用姿势与最佳实践

Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

Git的正确使用姿势与最佳实践

01 Git的前世今生

  • Git 是什么:Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

1.1 版本控制

  • 版本控制是什么:一种记录一个或若干个文件内容变化,以便将来查阅特定版本修订情况的系统。
  • 为什么需要版本控制:更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;也能够随时切换到不同的版本,回滚误删误改的问题代码。
版本控制类型代表性工具解决的问题
本地版本控制RCS本地代码的版本控制
集中式版本控制SVN提供一个远端服务器来维护代码版本,本地不保存代码版本,解决多人协作问题
分布式版本控制Git每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题

1.1.1 本地版本控制

  • 最初的方式:通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本。
  • 解决方案:开发了一些本地的版本控制软件,其中最流行的是 RCS
  • 基本原理:本地保存所有变更的补丁集,可以理解成就是所有的 Diff,通过这些补丁,我们可以计算出每个版本的实际的文件内容。
  • 缺点:RCS 这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因此衍生出了集中式版本控制。

1.1.2 集中版本控制

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

1.1.3 分布式版本控制

  • 代表性工具:Git

  • 基本原理:

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

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

    1. 相对 SVN 更复杂,学习成本更高。
    2. 对于大文件的支持不是特别好(git-lfs 工具可以弥补这个功能)。

1.2 Git平台

  • Github:全球最大的代码托管平台,大部分的开源项目都放在这个平台上。
  • Gitlab:全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成 Gitlab 的搭建。
  • Gerrit:由 Google 开发的一个代码托管平台,Android 这个开源项目就托管在 Gerrit 之上。

02 Git基本使用方式

2.1 Git目录介绍

2.1.1 Git Config

  • Git 有不同级别的配置:

    • **全局配置 global**:~/.gitconfig
    • **系统配置 system**:$(prefix)/etc/gitconfig
    • **本地配置 local**:.git/config

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

2.1.2 常见Git配置

  • 用户名配置:

    1
    2
    git config --global user.name "username'
    git config --global user.email youremail
  • Instead of 配置:

    1
    git config --global url.git@github.com:.insteadOf https://github.com/
  • Git 命令别名配置:

    1
    git config --global alias.cin "commit --amend --no-edit'"

2.2 Git Remote

  • 查看 remote 仓库:git remote -v

  • 添加 remote 仓库:

    1
    2
    git remote add origin_ssh git@github.com:git/git.git
    git remote add origin_http https://github.com/git/git.git
  • 同一个 Origin 设置不同的 PushFetch URL:

    1
    git remote set-url --add --push origin git@github.com:MY_REPOSITY/git

2.2.1 HTTP Remote

  • URL:https://github.com/git/git.git

2.2.2 SSH Remote

  • URL:git@github.com:git/git.git

  • 免密配置:SSH 可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问。

    目前的 Key 的类型四种,分别是 dsarsaecdsaed25519。默认使用的是 rsa,由于一些安全问题,现在已经不推荐使用 dsarsa 了,**优先推荐使用 ed25519**。

2.3 Git Add

  • git add <filename>: 添加文件到暂存区
    • git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件
    • git add 是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为 “精确地将内容添加到下一次提交中” 而不是 “将一个文件添加到项目中” 要更加合适。

2.4 Git Commit

  • git commit:创建一个新的提交,并启动你选择的文本编辑器来输入提交说明。
    • git commit -m "message":将提交信息与命令放在同一行,而不必打开文本编辑器来输入说明提交
    • git commit -aGit 会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤

2.5 Objects

  • Git 中的对象可以是 blob、树或提交:
    1. 通过 commit 寻找到 tree 信息,每个 commit 都会存储对应的 tree ID
    2. 通过 tree 存储的信息,获取到对应的目录树信息;
    3. tree 中获得 blobID,通过 blob ID 获取对应的文件内容。

2.6 Refs

  • Refs 文件存储的内容就是对应的 Commit ID,因此把 Ref 当做指针,指向对应的 Commit 来表示当前Ref 对应的版本。
  • refs/heads 前缀表示的是分支,除此之外还有其他种类的 ref,比如 refs/tags 前缀表示的是标签。
    • 分支一般用于开发阶段,是可以不断添加 commit 进行迭代的;
    • 标签一般表示的是一个稳定版本,指向的 commit 一般不会变更。

2.7 Annotation Tag

  • 附注标签:一种特殊的 tag,可以给 tag 提供一些额外的信息。
    • 通过 git tag -a 命令来完成附注标签的创建。

2.8 追溯历史版本

  • 获取当前版本代码:通过 Ref 指向的 Commit 可以获取唯一的代码版本。
  • 获取历史版本代码:Commit 里面会存有 parent commit 字段,通过 commit 的串联获取历史版本代码。

2.9 修改历史版本

  1. git commit --amend:通过这个命令可以修改最近的一次 commit 信息,修改之后 commit id 会变。
  2. rebase:通过 git rebase-i HEAD~3 可以实现对最近三个 commit 的修改:
    • 合并 commit
    • 修改具体的 commit message
    • 删除某个 commit
  3. filter --branch:该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作。

2.10 Git GC

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

  • Reflogreflog 是用于记录操作日志,防止误操作后数据丢失,通过 reflog 来找到丢失的数据,手动将日志设置为过期。

    1
    git reflog expire --expire=now --all
  • 指定时间:git gc --prune=now 指定的是修剪多久之前的对象,默认是两周前。

2.11 Git clone & pull & fetch

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

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

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

    可能存在冲突,需要解决冲突。

2.12 Git push

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

03 Git的研发流程

3.1 不同的工作流

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

3.2 集中式工作流

  • 只依托于 master 分支进行研发活动。
  • 工作方式:
    1. 获取远端 master 代码;
    2. 直接在 master 分支完成修改;
    3. 提交前拉取最新的 master 代码和本地代码进行合并(使用 rebase ),如果有冲突需要解决冲突;
    4. 提交本地代码到 master

3.3 分支管理工作流

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

3.3.1 分支管理工作流-Git Flow

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

3.3.2 分支管理工作流-Github Flow

  • Github 的工作流,只有一个主干分支,基于 Pull Request 往主干分支中提交代码。
  • 选择团队合作的方式:
    • owner 创建好仓库后,其他用户通过 fork 的方式来创建自己的仓库,并在 fork 的仓库上进行开发;
    • owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发。

3.3.3 分支管理工作流-Gitlab Flow

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

  • 原则:upstream first(上游优先),只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是 master

3.4 代码合并

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

3.5 如何选择合适的工作流

  • 没有最好的,只有最合适的。

  • 针对小型团队合作,推荐使用 Github Flow 即可。

    1. 尽量保证少量多次,最好不要一次性提交上千行代码;
    2. 提交 Pull Request 后最少需要保证有 CR 后再合入;
    3. 主干分支尽量保持整洁,使用 fast-forward 合入方式,合入前进行 rebase
  • 大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中。

---------------The End---------------