前言
这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记,做笔记记录一下自己的学习过程。
此笔记主要内容如下:
- Git 是什么
- Git 基本使用方式
- Git 研发流程
为什么要学习Git
- 协同工作
- 业界绝大多数公司都是基于 Git 进行代码管理,因此 Git 是一个程序员的必备技能
- 开源社区
- 目前绝大多数的开源项目都是基于 Git 维护的,参与这些项目的开发都需要使用 Git 。
1 Git是什么
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.
- Git 是一个免费的、开源的分布式版本控制系统,旨在快速高效地处理从小型到非常大型的所有项目。
- 版本控制是什么?
- 一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统
- 为什么需要版本控制?
- 更好的关注变更,了解到每个版本的改动是什么,方便对改动的代码进行检查,预防事故发生;
- 也能随时切换到不同的版本,回滚误删误改的问题代码; | 版本控制类型 | 代表性工具 | 解决的问题 | | :---: | :---: | :---: | | 本地版本控制 | RCS | 本地代码的版本控制 | | 集中式版本控制 | SVN | 提供一个远程服务器来维护代码版本,本地不保存代码版本,解决多人协作问题 | | 分布式版本控制 | Git | 每个仓库都能记录版本历史,解决只有一个服务器保存版本的问题 |
1.1.1 本地版本控制
- 最初的方式
- 通过本地复制文件夹,来完成版本控制,一般可以通过不同的文件名来区分版本
- 解决方案
- 开发了一些本地的版本控制软件,其中最流行的是 RCS
- 基本原理
- 本地保存所有变更的补丁集,可以理解成就是所有的 Diff ,通过这些补丁,我们可以计算出每个版本的实际文件内容
- 缺点
- RCS 这种本地版本控制存在最致命的缺陷就是只能在本地使用,无法进行团队协作,因此使用的场景非常有限,因此衍生出了集中式版本控制
1.1.2 集中版本控制
- 代表性工具: SVN
- 基本原理:
- 提供一个远端服务来保存文件,所有用户的提交都提交到该服务器中
- 增量保存每次提交的 Diff ,如果提交的增量中和远端现存的文件存在冲突,则需要本地提前解决冲突
- 优点:
- 学习简单,更容易操作
- 支持二进制文件,对大文件支持更友好
- 缺点:
- 本地不存储版本管理的概念,所有提交都只能联上服务器后才可以提交
- 分支上的支持不够好,对于大型项目团队合作比较困难
- 用户本地不保存所有的版本代码,如果服务端故障容易导致历史版本的丢失
1.1.3 分布式版本控制
- 代表性工具: Git
- 基本原理:
- 每个库都存有完整的提交历史,可以直接在本地进行代码提交
- 每次提交记录的都是完整的文件快照,而不是记录增量
- 通过 Push 等操作来完成和远端代码的同步
- 优点:
- 分布式开发,每个库都是完整的提交历史,支持本地提交,强调个体
- 分支管理功能强大,方便团队合作,多人协同开发
- 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容易导致代码丢失
- 缺点:
- 相对 SVN 更复杂,学习成本更高
- 对于大文件的支持不是特别好 ( git-lfs 工具可以弥补这个功能)
1.2 Git 发展历史
- 作者
- Linus Torvalds (就是 Linux 这个项目的作者,同时也是 Git 的作者)。
- 开发原因
- 怀疑 Linux 团队对 BitKeeper (另一种分布式版本控制系统,专有软件)进行了逆向工程,BitKeeper 不允许 Linux 团队继续无偿使用。因此决定自己开发一个分布式版本控制系统。
- 开发时间
- 大概花了两周时间,就完成了 Git 的代码第一个版本,后续 Linux 项目就开始使用 Git 进行维护。
- Github
- 全球最大的代码托管平台,大部分的开源项目都放在这个平台上。
- Gitlab
- 全球最大的开源代码托管平台,项目的所有代码都是开源的,便于在自己的服务器上完成 Gitlab 的搭建。
- Gerrit
- 由 Google 开发的一个代码托管平台,Android 这个开源项目就托管在 Gerrit 之上。
2 Git 的基本使用方式
- Git 的基本命令
2.1 Git 目录介绍
- 项目初始化
mkdir testcd testgit init
- 其他参数
- --initial-branch 初始化的分支
- --bare 创建一个裸仓库(纯 Git 目录,没有工作目录)
- --template可以通过模板来创建预先构建好的自定义 Git 目录
- Git 仓库
- 工作区 & 暂存区
——图源自liaoxuefeng.com
2.1.1 Git Config
- 不同级别的 Git 配置
- --global
- 存在当前用户~/.gitconfig
- --system
- 存在系统的/gitconfig
- 级别最高
- --local
- 存在当前仓库目录.git/config ,即上面截图的config
- 级别最低
- --global
- 每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置
2.1.2 常见 Git 配置
- 用户名配置
git config --global user.name "LYY"git config --global user.email "Cap_Jan_Lee@qq.com"
- Instead of 配置
- 用作url替换,比如用https协议作克隆,可以把ssh换成https,反之同理。
git config --global url.git@github.com:.insteadOf https://github.com/
- Git 命令别名配置
- 比如说把 commit --amend --no-edit 改成 cin
git config --global alias.cin "commit --amend --no-edit"
2.2 Git Remote
- 查看 Remote
git remote -v
- 添加 Remote
git remote add origin_ssh git@github.com:git/git.gitgit remote add origin_http https://github.com/git/git.git
- 同一个 Origin 设置不同的 Push 和 Fetch URL
2.2.1 HTTP Remote
- URL:github.com/git/git.git
- 免密配置
- 内存:
git config --global credential.helper 'cache --timeout=3600' - 硬盘:
git config --global credential.helper "store --file /path/to/credential-file" - 将密钥信息存在指定文件中
- 具体格式:
${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/id_ed25519.pub- 生成后 cat 打开该文件~/.ssh/id_ed25519.pub,复制到Github添加SSH and GPG keys即可
2.3 Git Add
git add .- 把文件修改添加到暂存区
touch readme.md
vim readme.md //写入Hello World
git status
git add .
git status
tree .git
- 此时 objects 文件夹下多了文件,有一个 objectID ,虽然是加密存储,但可以通过
git cat-file -p 557db***(objectID)命令查看新增的内容
2.4 Git Commit
git commit -m "add readme"- 实际上就是把暂存区的所有内容提交到当前分支
- 此时观察 .git 目录,新增两个 objectID
- 用 git cat-file -p objectID 查看内容,并查看 git log
2.5 Objects
- commit / tree / blob 在 git 里面都统一称为 Object,除此之外还有个 tag 的 Object。
- Blob :存储文件的内容
- Tree : 存储文件的目录信息
- Commit : 存储提交信息,一个 Commit 可以对应唯一版本的代码
- 三个信息如何串联到一起?
- 通过 Commit 寻找到 Tree 信息,每个 Commit 都会存储对应的 Tree ID。
- 通过 Tree 存储的信息,获取到对应的目录树信息。
- 从 Tree 中获得 Blob 的 ID,通过 Blob ID 获取对应的文件内容。
——图源自青训营PPT
- 通过 Commit 寻找到 Tree 信息,每个 Commit 都会存储对应的 Tree ID。
2.6 Refs
git checkout -b test创建一个新分支cat .git/refs/heads/master和cat .git/refs/heads/test当前指向是一样的- Refs 文件存储的内容 就是 对应的 Commit ID。因此把 ref 当做指针,指向对应的 Commit 来表示当前 Ref 对应的版本。
- 不同种类的 ref
- refs/heads 前缀表示的是分支,除此之外还有其他种类的 ref,比如 refs/tags 前缀表示的是标签。
- Branch
git checkout -b branchName可以创建一个新分支- 分支一般用于开发阶段,是可以不断添加 Commit 进行迭代的
- Tag
- 标签一般表示的是一个稳定版本,指向的 Commit 一般不会变更 (迭代发布用tag)
- 通过
git tag 版本号命令生成 tag
git tag v0.0.1
2.7 Annotation Tag
- 附注标签:一种特殊的 Tag,可以给 Tag 提供一些额外的信息。
- 通过
git tag -a命令可以完成附注标签的创建。 git tag -a v0.0.2 -m "add feature 1"- 此时objects目录下多了一个objectID,是v0.0.2指向的objectID,里面保存着一些附注信息。
- 此时objects目录下多了一个objectID,是v0.0.2指向的objectID,里面保存着一些附注信息。
2.8 追溯历史版本
- 获取当前版本代码
- 通过 Ref 指向的 Commit 可以获取唯一的代码版本。
- 获取历史版本代码
- Commit 里面会存有 parent commit 字段,通过 commit 的串联获取历史版本代码。
- 修改文件,并提交,创建新的 commit
- 查看最新的 commit,新增了 parent 信息。
- 修改文件,并提交,创建新的 commit
- Commit 里面会存有 parent commit 字段,通过 commit 的串联获取历史版本代码。
2.9 修改历史版本
commit --amend通过这个命令可以修改最近的一次 commit 信息,修改之后 commit id 会变rebase通过 git rebase -i HEAD~3 可以实现对最近三个 commit 的修改:- 合并 commit
- 修改具体的 commit message
- 删除某个 commit
filter - branch该命令可以指定删除所有提交中的某个文件或全局修改邮箱地址等操作
- 通过
git commit --amend命令,尝试修改一下 commit message - 通过
git log发现最新的 commimt 已经从8da6179862a268422f889d8fa26aad01f72a2ed4变成37cf7be31a0c765c64112c2dd9fa1bc39496bdb7,但是 tree 和 blob 没变。
2.10 悬空的 Object
- 新增的 Object
- 修改 Commit 后我们可以发现 git object 又出现了变化
- 新增了 commit object 37
- 但是之前的 commit object 8d 并没有被删除,又没有 ref 指向它
- 悬空的 Object
- 顾名思义就是没有 ref 指向的 object
- 可以用
git fsck --lost-found查找悬空的 object
2.11 Git GC
- GC
- 通过 git gc 命令,可以删除一些不需要的 object,以及会对 object 进行一些打包压缩来减少仓库体积。
- Reflog
- reflog 是用于记录操作日志,防止误操作后数据丢失,通过 reflog 来找到丢失的数据,手动将日志设置为过期。
- 指定时间
git gc prune=now指定的是修剪多久之前的对象- 默认是两周前,可以通过
git reflog expire --expire=now --all修改为现在 - git gc 后,观察 .git 目录,之前的 ref 和 object 会被打包,并没有删除,仅删除了悬空的 object
2.12 完整的 Git 视图
2.13 Git Clone & Pull & Fetch
- Clone
- 拉取完整的仓库到本地目录,可以指定分支、深度。
- Fetch(不熟悉远端代码是否被改动可以先fetch再手动合并冲突)
- 将远端某些分支最新代码拉取到本地,不会执行 merge 操作,
- 会修改 refs/remote 内的分支信息,如果需要和本地代码合并需要手动操作。
- Pull(熟悉远端代码可以直接pull合并本地代码)
- 拉取远端某分支,并和本地代码进行合并,操作等同于 git fetch + git merge,
- 也可以通过 git pull --rebase 完成 git fetch + git rebase 操作
- 可能存在冲突,需要解决冲突。
2.14 Git Push
Push是将本地代码同步至远端的方式
- 常用命令
- 一般使用
git push origin master命令即可完成
- 一般使用
- 冲突问题
- 如果本地的 commit 记录和远端的 commit 历史不一致,则会产生冲突,比如
git commit --amendorgit rebase都有可能导致这个问题。 - 如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过
git push origin master -f来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。
- 如果本地的 commit 记录和远端的 commit 历史不一致,则会产生冲突,比如
- 推送规则限制
- 可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。
- 可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。
常见问题
- 为什么我明明配置了 Git 配置,但是依然没有办法拉取代码?
- 免密认证没有配。
- Instead Of 配置没有配,配的 SSH 免密配置,但是使用的还是 HTTP 协议访问。
- 为什么我 Fetch 了远端分支,但是我看本地当前的分支历史还是没有变化?
- Fetch 会把代码拉取到本地的远端的分支,但是并不会合并到当前分支,所以当前分支历史没有变化。