学习流程:
为什么要学习Git
协同工作: 业界绝大多数公司都是基于Gt进行代码管理,因此Gt是一个程序员的必备技能
开源社区: 目前绝大多数的开源项目都是基于Gt维护的,参与这些项目的开发都需要使用Git。
常见问题
1.入职后按照文档进行Gt配置,但是配置后依然拉取代码有问题,缺少自己排查配置问题的能力
2.研发流程中进行一些异常操作,不符合研发规范,不清楚保护分支,MR/PR等概念
学习目标
1.学习基本的Gt命令,并了解原理,在遇到Gt相关问题时,能自行排查并解决
2.了解研发流程中的基本概念和规范,学会正确的使用Gt
目录
01.Git是什么: 介绍版本控制的发展历史,为什么会出现Git 介绍Git的发展历史,为什么要学习Git
02.Git基本使用方式: Git的基本命令介绍,如何使用这些命令,以及命令的原理
03.Git研发流程: 依托代码管理平台Gitlab/Github/Gerrit介绍我们如何进行代码的开发及团队合作
01Git是什么
1.1版本控制
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.
版本控制是什么?
一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
为什么需要版本控制?
更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生 也能够随时切换到不同的版本,回滚误删误改的问题代码;
| 版本控制类型 | 代表性工具 | 解决的问题 |
|---|---|---|
| 本地版本控制 | RCS | 本地代码的版本控制 |
| 集中式版本控制 | SVN | 提供一个远端服务器来维护代码版本,本地不保存代码版本 |
| 分布式版本控制 | Git | 解决多人协作问题 每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题 |
1.1.1本地版本控制
最初的方式: 通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本 解决方案: 开发了一些本地的版本控制软件,其中最流行的是RCS
基本原理 本地保存所有变更的补丁集,可以理解成就是所有的D仟,通过这些补丁,我们可以计算出每个版本的实际的文件内容
缺点 RCS这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因 此衍生出了集中式版本控制
1.1.2集中式版本控制
代表性工具:SVN
基本原理:
1.提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中
2.增量保存每次提交的Diff,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
优点
1.学习简单,更容易操作 2.支持二进制文件,对大文件支持更友好
缺点:
1.本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交
2.分支上的支持不够好,对于大型项目团那队合作比较困难
3.用户本地不保存所有版本的代码,如果服务端故障容易导致历史版本的丢失。
1.1.3分布式版本控制
代表性工具:Git 基本原理
1.每个库都存有完整的提交历史,可以直接在本地进行代码提交 2.每次提交记录的都是完整的文件快照,而不是记录增量 3.通过Push等操作来完成和远端代码的同步
优点:
1.分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体 2.分支管理功能强大,方便团队合作,多人协同开发 3.校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失
缺点:
1.相对SVN更复杂,学习成本更高
2.对于大文件的支持不是特别好(Git-fs工具可以弥补这个功能)
1.2 Git 发展历史
作者 Linus Torvalds(Linux项目的作者,同时也是Git的作者)。 开发原因: 怀疑Linux团队对BitKeeper(另一种分布式版本控制系统,专有软件)进行了逆向工程,BitKeeper不允许Linux团队继续无偿使用。因此决定自己开发一个分布式版本控制系统。 开发时间 大概花了两周时间,就完成了Git的代码第一个版本,后续Liux项目就开始使用Git进行维护。
github:全球最大的代码托管平台,大部分的开源项目都放在这个平台上。
gitlab:全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成Gitlb的搭建。
gerrit:由Google开发的一个代码托管平台,Android这个开源项目就托管在Gerrit之上
02 Git的基本使用方式
graph LR
A(Git命令)-->B(配置)
A-->C(提交代码)
A-->D(远端同步)
B-->B1(git config)
B-->B2(git remote)
C-->C1(git add)
C-->C2(git commit)
D-->D1(拉取代码)
D-->D2(推送代码)
D1-->D11(clone)
D1-->D12(pull)
D1-->D13(fetch)
D2-->D21(push)
2.1 Git目录介绍
项目初始化
mkdir study
cd study
git init
D:\Desktop\code\Go\青训营\day7Git的正确使用姿势\demo>git init Initialized empty Git repository in D:/Desktop/code/Go/青训营/day7Git的正确使用姿势/demo/.git/
D:\Desktop\code\Go\青训营\day7Git的正确使用姿势\demo>
其他参数 --initial-branch初始化的分支 --bare创建一个裸仓库(纯Git目录,没有工作目录) --template可以通过模版来创建预先构建好的自定义git目录
tree.git
卷 Data 的文件夹 PATH 列表 卷序列号为 00000095 1E44:BF5D D:\DESKTOP\CODE\GO\青训营\DAY7GIT的正确使用姿势\DEMO.GIT
2.1.1 git config
不同级别的Git配置
--system 系统配置 For writing options:write to system-wide (prefix)/etc/gitconfig rather than the repository .git/config. For reading options:read only from system-wide (prefix)/etc/gitconfig rather than from all available files. See also the section called "FILES".
--global 全局配置 For reading options:read only from global ~/.gitconfig and from SXDG_CONFIG_HOME/git/config rather than from all available files. See also the section called "FILES".
--local 本地配置 For writing options:write to the repository .git/config file.This is the default behavior. For reading options:read only from the repository .git/config rather than from all available files. See also the section called "FILES".
每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置,上面的级别从高到低
2.1.2常见配置
用户名配置
git config --global user.name "yanhuozhuoshi"
git config --global user.email 2756873752@qq.com
Instead of配置
git config --global url.git@github.com:.insteadOf https://github.com/
Git命令别名配置
git config --global alias.cin "commit --amend --no-edit"
[alias] cin commit --amend --no-edit
将commit改为cin
2.2 Git Remote
表示我们本地和远端仓库的关联信息,remote一般分为http和ssh两种
查看Remote
git remote -v
添加Remote:
添加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:一个仓库拥有多个remote,从没有写权限的仓库fetch代码,push到自己有权限的仓库
git remote set-url --add --push orogin git@github.com:MY_REPOSITY/git origin git@github/git (tecth)
origin git@github.com:MY_REPOSITY/git (push)
2.2.1 HTTP Remote
(引出免密配置)我们知道了什么是remote配置后,那我们本地是如何与remote进行通信的呢, 一股会通过http和ssh两种协议,这两种协议都需要对身份进行认证,类似go这种语言,依赖库很多,所以我们需要不断的输入认证的账号密码,肯定是一件很麻烦的事情,因此我们需要配置一下免密的认证方式
免密配置 内存:git config-global credential.helper'cache-timeout:=3600 git config --global credential.helper "store --file /path/to/credential-file" 不指定目录的情况默认是~/.git-credentials 将密钥信息存在指定文件中 具体格式:{scheme}:/{user:${password)@github.com
2.2.2 SSH Remote
URL:git@github.com:git/git.git 免密配置 SSH可以通过公私钥的机制,将生成公钥存放在服务端,从而实现免密访问 目前的Key的类型四种,分别是dsa、rsa、ecdsa、ed25519
默认使用的是rsa,由于一些安全问题,现在已经不推荐使用dsa和rsa了,优先推荐使用ed25519
ssh-keygen-t ed25519 -C "your email@example.com”密钥默认存在~/.ssh/ided25519.pub
2.3 Git Add
新建一个文件readme.md
并添加到暂存区:git add .
objects
├─e6
├─info
└─pack
2.4 Git commit
git commit之后再使用git status
2.5Objects
commit/tree/blob在git里面都统一称为Object,除此之外还有个tag的object
Blob:存储文件的内容 Tree:存储文件的目录信息 Commit:存储提交信息,一个Commit可以对应唯一版本的代码
如何把这三个信息串联在一起呢?
1.通过Commit寻找到Tree信息,每个Commit都会存储对应的Tree ID.
git cat-fi1e-p373137d6db9018d97279384fb5644e880a9c1b1b
2.通过Tree存储的信息,获取到对应的目录树信息。
git cat-file -p 239ec593c6a2192e76c005435f748b2ad28be832
3.从tree中获得blob的ID,通过Blob ID获取对应的文件内容
git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
2.6 Refs
Refs文件存储的内容
refs的内容就是对应的Commit ID 因此把ref当做指针,指向对应的Commit来表示当前Ref对应的版本。
不同种类的ref refs/heads前缀表示的是分支,除此之外还有其他种类的ref,比如refs/tags前缀表示的是标签。
Branch git checkout-b可以创建一个新分支 分支一般用于开发阶段,是可以不断添加Commit进行迭代的
Tag 标签一般表示的是一个稳定版本,指向的Commit一般 不会变更,通过git tag命令生成tag
git tag v0.0.1
tags目录下就会多出一个文件v0.0.1
2.7 Annotation Tag
什么是附注标签? 一种特殊的Tag,可以给Tag提供一些额外的信息。 如何创建附注标签? 通过git tag -a命令来完成附注标签的创建。
查看该tag object的内容。
2.8追溯历史版本
获取当前版本代码 通过Ref指向的Commit可以获取唯一的代码版本。 获取历史版本代码 Commit里面会存有parent commit字段,通过commit的串联获取历史版本代码。
2.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 该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作
2.10 Objects
新增的Object 修改Commit后我们可以发现git object又出现了变化 新增commit object 7f 但是之前的commit object 63并没有被删除
悬空的Object 顾名思义就是没有ref指向的object
git fsck --lost-found
2.11 Git GC
GC 通过git gc命令,可以删除一些不需要的object, 以及会对object进行一些打包,压缩来减少仓库的体积。
Reflog reflog是用于记录操作日志,防止误操作后数据丢失 通过reflog来找到丢失的数据,手动将日志设置为过期。
指定时间 git gc prune=now指定的是修剪多久之前的对象 默认是两周前
2.12 完整Git视图
2.13 Git Clone Pull Fetch
Clone 拉取完整的仓库到本地目录,可以指定分支,深度。 Fetch 将远端某些分支最新代码拉取到本地,不会执行merge操作, 会修改refs/remote内的分支信息,如果需要和本地代码合并需要手动操作。 Pull
拉取远端某分支,并和本地代码进行合并,操作等同于git fetch+git merge, 也可以通过git pull-rebase完成git fetch+git rebase操作。 可能存在冲突,需要解决冲突。
2.14 Git Push
push是将本地代码同步至远端的方式。
常用命令 一般使用git push origin master命令即可完成
冲突问题
1.如果本地的commit记录和远端的commit历史不一致,则会产生冲突,比如git commit-amend or git rebase都有可能导致这个问题。
2.如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过git push origin master一f 来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
推送规则限制 可以通过保护分支,来配置一些保护规侧,防止误操作,或者一些不合规的操作出现,导致代码丢失。
常见问题
1.为什么我明明配置了Git配置,但是依然没有办法拉取代码? 免密认证没有配。 Instead Of配置没有配,配的SSH免密配置,但是使用的还是HTTP协议访问。
2.为什么我Fetch了远端分支,但是我看本地当前的分支历史还是没有变化? Ftch会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。
03 Git研发流程
讲完了在本地Git的操作,接下来我们会讲述一下如何依托于代码管理平台之上来完成团队的协作,这个协作的流程是怎样的,我们应该怎么操作才是合理的
03.常见问题 1.在Gerrit平台上使用Merge的方式合入代码 2.不了解保护分支,Code Review,CI等概念,研发流程不规范 3.代码历史混乱,代码合并方式不清淅
不同的工作流
| 类型 | 代表平台 | 特点 | 合入方式 |
|---|---|---|---|
| 集中式工作流 | Gerrit、 SVN | 只依托于主干分支进行开发,不存在其他分支 | Fast-forward |
| 分支管理工作流 | Github Gitlab | 可以定义不同特性的开发分支,上线分支,在开发分支完成开发后再通过MR/PR合入主干分支 | 自定义,Fast-Forward orThree-Way Merge都可以 |
3.2集中式工作流
什么是集中式工作流? 只依托于master分支进行研发活动 工作方式
1.获取远端master代码
2.直接在master分支完成修改 3.提交前拉取最新的master代码和本地代码进行合并(使用rebase),如果有冲突需要解决冲突 4.提交本地代码到master
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时比较早期出现的分支管理策略。 包含五种类型的分支
Master:主干分支 Develop:开发分支 Feature:特性分支 Release:发布分支 Hotfix:热修复分支
优点
如果能按照定义的标准严格执行,代码会很清晰,并且很难出现混乱。
缺点
流程过于复杂,上线的节奏会比较慢。由于太复杂,研发容易不按照标准执行,从而导致代码出现混乱。
3.3.2 Github Flow
Github的工作流,只有一个主干分支,基于Pull Request往主干分支中提交代码。 选择团队合作的方式
1.owner创建好仓库后,其他用户通过Fork的方式来创建自己的仓库,并在fork的仓库上进行开发
2.owner创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发
创建一个Pull Request
1.创建一个main主分支
在github上创建后本地进行拉取,添加一个文件后提交
vim readme git add . git commit -m "add readme"2.创建一个feature分支
切换到新的分支,并对readme文件进行修改后提交
git checkout -b feature //修改文件:... git add . git commit -m "update readme"3.创建一个feature到main的Pull Request
可以在Pull Request页面执行CI/CA/CR等操作,都检查通过后,执行合入。
可以通过进行一些保护分支设置,来限制合入的策略,以及限制直接的push操作。
3.3.2分支管理工作流-Gitlab Flow
Gitlab推荐的工作流是在GitFlow和Github Flow上做出优化,既保持了单一主分支的简便,又可以适应不同的开发环境。 原则:upstream first 上游优先 只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是master.
总结:这几个工作流的共性是都需要通过git merge来合入代码,从而引出介绍代码合并的方式
3.4代码合并
Fast-Forward 不会产生个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新 source branch后才可以合入。
Three-Way Merge 三方合并,会产生一个新的merge节点
3.5如何选择怿合适的工作流
选择原则 没有最好的,只有最合适的 针对小型团队合作,推荐使用Github工作流即可
1.尽量保证少量多次,最好不要一次性提交上干行代码
2.提交Pull Request后最少需要保证有CR后再合入
3.主干分支尽量保持整洁,使用fast-forward合入方式,合入前进行rebase
大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中
03.常见问题
1.在Gerrit平台上使用Merge的方式合入代码。 Gerrit是集中式工作流,不推荐使用Merge方式合入代码,应该是在主干分支开发后,直接Push。
2.不了解保护分支,Code Review,CI等概念,研发流程不规范。 保护分支:防止用户直接向主干分支提交代码,必须通过PR来进行合入。 Code Review,CIl:都是在合入前的检查策略,Code Review是人工进行检查,C则是通过一些定制化的脚本来进行一些校验。
3.代码历史混乱,代码合并方式不清晰。 不理解Fast Forward和Three Way Merge的区别,本地代码更新频繁的使用Three Way的方式,导致生成过多的Merge节点,使提交历史变得复杂不清晰。
总结:Git是一个分布式版本控制工具,由linus开发,衍生出github gitlab gerrit等平台 Git配置,Git代码提交,Git代码同步基本命令,以及git管理代码的原理,帮助我们更好的知道如何正确使用Git命令 讲述不同的研发流程,有以gerrit为代表的集中式工作流,和gitlab/github为代表的分支管理工作流,讲述了一些代码提交规范,保护分支,codereview等概念,帮助我们规范研发流程