Git 是一个开源的分布式版本控制系统,常用于协同开发和管理项目代码。它由Linus Torvalds在2005年创建,并成为了许多开发团队和开源社区的首选工具。
Git 的主要特点包括:
- 分布式: 每个开发者都拥有完整的代码仓库副本,包括完整的历史记录。这意味着即使在离线状态下,开发者仍然可以进行提交、分支切换等操作,不会受到中心化版本控制系统的限制。
- 版本控制: Git 记录了项目代码的每个版本和变更。通过提交(commit)操作,开发者可以记录代码的修改并添加相应的注释说明。这样可以轻松地查看和比较不同版本之间的差异,并追踪代码的演进。
- 分支: Git 提供强大的分支机制,可以创建新的分支来独立开发特性或修复错误,而不影响主线代码。分支操作快速且轻量,使得团队成员可以并行进行工作,然后再将各自的分支合并到主线上。
- 合并: Git 提供了多种合并策略,使得将不同分支的代码合并变得简单。能够智能地处理代码冲突,并提供易于理解的合并历史。
- 代码审查: Git 支持团队中的代码审查流程。开发者可以通过查看提交(commit)的变更,并在合并请求(pull request)中进行讨论和评审,改进代码质量和团队协作。
- 轻量且高效: Git 的设计简单而高效,具有出色的性能表现。由于每个开发者都拥有完整的代码仓库副本,因此可以快速进行各种操作,如提交、切换分支、查看历史等。
为什么要学习 Git ?
- 业界绝大多数公司基于 Git 进行代码管理,因此 Git 是一个程序员的必备技能
- 目前绝大多数开源社区都基于 Git 维护,参与项目开发都需要使用 Git
1 Git 是什么
- 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 本地版本控制
- 本地版本控制通过复制本地文件夹来完成版本控制,一般可通过不同的文件名来区分版本
- 基本原理是本地保存所有变更的补丁集,通过这些补丁,我们可以计算出每个版本的实际文件内容
- 这种版本控制的缺点是只能在本地使用,无法进行团队协作,使用场景十分有限
1.1.2 集中式版本控制
代表工具: SVN
基本原理
- 提供一个远端服务来保存文件,所有用户的捉交都捉交到该服务器中
- 增量保存每次提交的 diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
优点
- 学习简单,更容易操作
- 支持二进制文件,对大文件支持更友好
缺点
- 本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交
- 分支上的支特不好,对于大型项目团队合作比北较困难
- 用户本地不保存所有版本的代码,如果服务端故障客易导致历史版本的丢失。
1.1.3 分布式版本控制
代表工具:Git
基本原理
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不足记录增量
- 通过 Push 等操作来完成和远端代码的同步
优点
- 分布式开发,每个库都是完空的提交历史,支持本地提交,强调个体
- 分支管理功强大,方便团队合作,多人协同开发
- 校验和机制保证完整性,一股只添数据,很少执行删除操作,不容易号致代码丢失
1.2 Git 发展历史
Git 的作者 Linus Torvalds 也是 Linux 项目的作者。由于 BitKeeper (另一种分布式版本控制系统,专有软件)不允许 Linux 团队继续无偿使用,因此 Linux 团队决定自己开发一个分布式版本控制系统,团队大概花了两周时间就完成了 Git 的代码第一个版本,后续Liux项目就开始使用 Git 进行维护。
Git 是当下最流行的版本控制工具,基于 Git 衍生出了很多代码托管平台
2 Git 的基本使用方法
2.1 Git 目录介绍
新建一个文件夹,执行git init
命令
git init
# 其他参数
--initial-branch 初始化的分支
--bare 创建一个裸仓库(纯Git目录,没有工作目录)
--template 可以通过模板来创建构建好的自定义 git 目录
文件夹中会生成一个 .git 目录,我们后续的操作都会映射到这个目录中
.git
├── config
├── HEAD
├── hooks
│ └── commit-msg.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
2.2 Git 配置
我们看到 .git 目录里面有一个 config 文件,里面的内容就是 git 的配置,我们配置不同级别的配置,每个级别的配置可能重复,低级别的配置会覆盖高级别的配置
2.2.1 Git config 配置
常见的 Git 配置
# 用户名配置
git config --global user.name <username>
git config --global user.email <useremail>
# git 命令别名配置
git config --global alias.cin "commit --amend --no-edit"
2.2.2 Git remote 配置
除了以上基本配置,还有一种 remote 配置,表示本地和远程仓库的关联信息
# 查看 Remote
git remote -v
# 添加 remote
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 add origin git@github.com:git/git
git remote set-url --add --push origin git@github.com:MY_REPOSITY/git
git remote -v
2.3 Git add
git add 的命令是将代码提交到暂存区,执行命令前后变化
2.4 Git commit
git commit 用于向仓库提交暂存代码,提交后 objects 和 refs 文件夹会有变化
2.4.1 Objects
commit/tree/blob在 git 里面都统一称为 Object, 除此之外还有个 tag 的 object.
- Blob:存储文件的内容
- Tree:存储文件的目录信息
- Commit:存储提交信息,一个 Commit 可以对应唯一版本的代码
2.4.2 Refs
refs 的内容就是对应的 Commit ID,因此把 ref 当作指针,指向对应 Commit 来表示当前 ref 对应版本
refs / heads 前缀表示的是分支,除此以外还有其他种类的 ref,比如 refs / tags 表示标签
- Branch (分支)
利用 git checkout -b
可以创建一个分支,用于开发阶段不断 commit 进行迭代
- Tag (标签)
利用git tag
命令生成标签,一般表示一个稳定版本,指向的 commit 一般不会变更
2.5 Git gc
GC
通过 git gc 命令,可以删除一些不需要的 object,以及会对 object 进行一些打包,压缩来减少仓库的体积。
Reflog
reflog 是用于记录操作日志,防止误操作后数据丢失通过 reflog 来找到丢失的数据,手动将日志设置为过期。
指定时间
git gc prune=now
指定的是修剪多久之前的对象,默认是两周前
2.6 Git clone & pull & fetch
Clone
拉取完整的仓库到本地目录,可以指定分支,深度。
Fetch
将远端某些分支最新代码拉取到本地,不会执行 merge 操作,会修改 refs/remote 内的分支信息,如果需要和本地代码合井需要手动操作。
Pull
拉取远端某分支,并和本地代码进行合并,操作等同于git fetch+git merge,也可以通过 git pull --rebase 完成 git fetch+git rebase 操作。可能存在冲突,需要解决冲突。
2.7 Git push
git push 是将本地代码同步至远端的方式
常用命令
一般使用git push origin master
命令即可完成
冲突问题
1.如果本地的 commit 记录和远端的 commit 历史不一致,则会产生冲突,比如 git commit --amend or git rebase
都有可能导致这个问题。
2.如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过 git push origin master -f
来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
推送规则限制
可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。
2.8 常见问题
1.为什么我明明配置了Gt配置,但是依然没有办法拉取代码?
- 免密认证没有配。
- Instead Of 配置没有配,配的 SSH 免密配置,但是使用的还是 HTTP 协议访问。
2.为什么我 Fetch 了远端分支,但是我看本地当前的分支历史还是没有变化?
- Fetch 会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。
3 Git 开发流程
3.1 不同的工作流
类型 | 代表平台 | 特点 |
---|---|---|
集中式工作流 | Gerrit / SVN | 只依托于主干分支进行开发,不存在其他分支 |
分支管理工作流 | Github / Gitlab | 可以定义不同特性的开发分支,上线分支,在开发分支完成开发后再通过MR/PR合入主干分支 |
3.1.1 集中式工作流
什么是集中式工作流?
只依托于 master 分支进行研发活动
工作方式
1.获取远端 master 代码
2百接在master分支完成改
3.提交前拉取最新的 master 代码和本地代码进行合并(使用 rebase),如果有冲突需要解决冲突
4.捉交本地代码到 master
3.1.2 分支管理工作流
分支管理工作流 | 特点 |
---|---|
Git Flow | 分支类型丰富,规范严格 |
Github Flow | 只有主干分支和开发分支,规则简单 |
Gitlab Flow | 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布 or 环境的需要 |
Git Flow
- 包含五种类型的分支
Master: 主干分支
Develop: 开发分支
Feature: 特性分支
Release: 发布分支
Hotfix: 热修复分支
- 优点
如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱。
- 缺点
流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按标准执行从而导致代码出现混乱。
Github Flow
Github的工作流,只有一个主干分支,基于 Pull Request 往主干分支中提交代码。
选择团队合作的方式
- owner 创建好仓库后,其他用户通过Fok的方式来创理自己的仓库,井在 fork 的仓库上进行开发
- owner 创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
创建一个Pull Request
- 创建一个main主分支
- 创建一个 feature 分支
- 创建一个 feature 到 main 的 Pull Request
- 可以在 Pull Request 页面执行 CI/CA/CR 等操作,都检查通过后,执行合入
- 可以通过进行一些保护分支设置,来限制合入的策略,以及限制直接的 push 操作
Gitlab Flow
Gitlab 推荐的工作流是在 Git Flow 和 Github Flow 上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境。
原则:upstream first上游优先
只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是 master.
3.2 代码合并
Fast-Forward
不会产生一个 merge 节点,合井后保持一个线性历史,如保 target 分支有了更新,则需要通过rebase 操作更新,source branch 后才可以合入
Three-Way Merge
三方合并,会产生一个新的 merge 节点
3.3 如何选择合适的开发流
选择原则
没有最好的,只有最合适的
针对小型团队合作,推荐使用Github工作流即可
- 尽量保证少量多次,最好不要一次性提交上干行代码
- 提交 Pull Request 后最少需要保证有 CR 后再合入
- 主干分支尽量保持整洁,使用 fast-forward 合入方式,合入前进行 rebase
3.4 常见问题
- 在Gerrit平台上使用Merge的方式合入代码。
Gerrit 是集中式工作流,不推荐使用 Merge 方式合入代码,应该是在主干分支开发后,直接Push。
- 不了解保护分支,Code Review,CI 等概念,研发流程不规范。
保护分支:防止用户直接向主干分支提交代码,必须通过 PR 来进行合入。
Code Review, CI: 都是在合入前的检查策略,Code Review 是人工进行检查,CI 则是通过一些定制化的脚本来进行一些校验。
- 代码历史混乱,代码合并方式不清晰。
不理解 Fast Forward 和 Three Way Merge 的区别,本地代码更新频繁的使用 Three Way 的方式,导致生成过多的 Merge 节点,使提交历史变得复杂不清晰。