Git的正确使用姿势与实践 | 青训营笔记

171 阅读9分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第 5 篇笔记

为什么要学习Git?

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

01.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,通过这些补丁,我们可以计算出每个版本的实际的文件内容。

缺点:只能本地使用,无法进行团队协作。

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-lfs工具可以弥补这个功能)

1.2 Git发展历史

Linux团队研发

Github 全球最大的代码托管平台,大部分的开源项目都放在这个平台上。

Gitlab 全球最大的开源代码托管平台。项目的所有代码都是开源的,便于在自己的服务器上完成gitlab的搭建。

Gerrit 由Google开发的一个代码托管平台,Android这个开源项目就托管在Gerrit之上。

02.Git的基本使用方式

Git基本命令

image-20220524110330115.png

2.1 Git目录介绍

项目初始化

mkdir study
cd study
git init

其他参数
--initial-branch 初始化的分支
--bare 创建一个裸仓库(纯Git目录,没有工作目录)
--template 可以通过模板来创建预先构建好的自定义git目录

Git仓库

image-20220524111549266.png

2.1.1 Git Config

不同级别的Git配置

--global

--system

--local

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

2.2.2 常见Git配置

用户名配置

git config --global user.name "xxx"
git config --global user.email xxx@xxx.com

Instead of 配置

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

Git命令别名配置

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

2.2 Git Remote

查看Remotegit 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

2.2.1 HTTP Remote

image-20220524113049812.png

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

image-20220524155558342.png

2.3 Git Add

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ touch readme.md

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git add .

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   readme.md


administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ tree .git
.git
|-- HEAD
|-- config
|-- description
|-- hooks
|   |-- applypatch-msg.sample
|   |-- commit-msg.sample
|   |-- fsmonitor-watchman.sample
|   |-- post-update.sample
|   |-- pre-applypatch.sample
|   |-- pre-commit.sample
|   |-- pre-merge-commit.sample
|   |-- pre-push.sample
|   |-- pre-rebase.sample
|   |-- pre-receive.sample
|   |-- prepare-commit-msg.sample
|   |-- push-to-checkout.sample
|   `-- update.sample
|-- index
|-- info
|   `-- exclude
|-- objects
|   |-- 95
|   |   `-- d09f2b10159347eece71399a7e2e907ea3df4f
|   |-- info
|   `-- pack
`-- refs
    |-- heads
    `-- tags

9 directories, 19 files

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 95d09f2b10159347eece71399a7e2e907ea3df4f
hello world

2.4 Git Commit

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git commit -m "add readme"
[master (root-commit) 610faa0] add readme
 1 file changed, 1 insertion(+)
 create mode 100644 readme.md

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ tree .git
.git
|-- COMMIT_EDITMSG
|-- HEAD
|-- config
|-- description
|-- hooks
|   |-- applypatch-msg.sample
|   |-- commit-msg.sample
|   |-- fsmonitor-watchman.sample
|   |-- post-update.sample
|   |-- pre-applypatch.sample
|   |-- pre-commit.sample
|   |-- pre-merge-commit.sample
|   |-- pre-push.sample
|   |-- pre-rebase.sample
|   |-- pre-receive.sample
|   |-- prepare-commit-msg.sample
|   |-- push-to-checkout.sample
|   `-- update.sample
|-- index
|-- info
|   `-- exclude
|-- logs
|   |-- HEAD
|   `-- refs
|       `-- heads
|           `-- master
|-- objects
|   |-- 61
|   |   `-- 0faa0c939bb5e638b62272b17143a4bfc74f22
|   |-- 95
|   |   `-- d09f2b10159347eece71399a7e2e907ea3df4f
|   |-- 9a
|   |   `-- a4816ed4d7cce80d7fa619412de2aebededcee
|   |-- info
|   `-- pack
`-- refs
    |-- heads
    |   `-- master
    `-- tags

14 directories, 25 files

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 610faa0c939bb5e638b62272b17143a4bfc74f22
tree 9aa4816ed4d7cce80d7fa619412de2aebededcee
author Yuzi_Yang <3190102636@zju.edu.cn> 1653395821 +0800
committer Yuzi_Yang <3190102636@zju.edu.cn> 1653395821 +0800

add readme

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git cat-file -p 9aa4816ed4d7cce80d7fa619412de2aebededcee
100644 blob 95d09f2b10159347eece71399a7e2e907ea3df4f    readme.md

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ git log
commit 610faa0c939bb5e638b62272b17143a4bfc74f22 (HEAD -> master)
Author: Yuzi_Yang <3190102636@zju.edu.cn>
Date:   Tue May 24 20:37:01 2022 +0800

    add readme

2.5 Objects

commit/tree/blob在git里面都统一称为Object,除此之外还有个tag的object.

Blob 存储文件的内容

Tree 存储文件的目录信息

Commit 存储提交信息,一个Commit可以对应唯一版本的代码

如何把这三个信息串联在一起呢

  1. 通过Commit寻找到Tree信息,每个Commit都会存储对应的Tree ID。

image-20220524204547136.png

  1. 通过Tree存储的信息,获取到对应的目录树信息。

image-20220524204618523.png

  1. 从tree中获得blob的ID,通过Blob ID获取对应的文件内容。

image-20220524204646936.png

image-20220524204721765.png

2.6 Refs

cat .git/refs/heads/master

Refs文件存储的内容

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (master)
$ cat .git/refs/heads/master
610faa0c939bb5e638b62272b17143a4bfc74f22

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ cat .git/refs/heads/test
610faa0c939bb5e638b62272b17143a4bfc74f22

refs的内容就是对应的Commit ID 因此把ref当做指针,指向对应的Commit来表示当前Ref对应的版本。

不同种类的ref refs/heads前缀表示的是分支,除此之外还有其他种类的ref,比如refs/tags前缀表示的是标签。

Branch git checkout -b可以创建一个新分支 分支一般用于开发阶段,是可以不断添加Commit进行迭代的。

Tag 标签一般表示的是一个稳定版本,指向的Commit一般不会变更。git tag命令生成tag

2.7 Annotation Tag

附注标签,一种特殊的Tag,可以给Tag提供一些额外的信息。

如何创建附注标签?

通过git tag -a命令来完成附注标签的创建。

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ git tag -a v0.0.2 -m "add feature 1"

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ cat .git/refs/tags/v0.0.2
f3965f5a388b8ed0432ccb12f856ac75f7f4fc7c

查看该tag object的内容:

administration@LAPTOP-V4GMFJON MINGW64 /e/桌面文件/demo (test)
$ git cat-file -p f3965f5a388b8ed0432ccb12f856ac75f7f4fc7c
object 610faa0c939bb5e638b62272b17143a4bfc74f22
type commit
tag v0.0.2
tagger Yuzi_Yang <3190102636@zju.edu.cn> 1653397197 +0800

add feature 1

2.8 追溯历史版本

  • 获取当前版本代码

通过Ref指向的Commit可以获取唯一的代码版本。

  • 获取历史版本代码

Commit里面会存有parent commit字段,通过commit的串联获取历史版本代码。

  1. 修改文件,并提交,创建新的commit。
  2. 查看最新的commit,新增了parent信息。

image-20220524211900282.png

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

该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作

image-20220524212723951.png

2.10 Objects

新增的Object

修改commit后我们可以发现git object又出现了变化

新增commit object 7f 但是之前的 commit object 63 并没有被删除

悬空的Object

顾名思义就是没有ref指向的object。git fsck --lost-found命令可以查询悬空的object。

2.11 Git GC

GC

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

Reflog

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

指定时间

git gc prune=now指定的是修剪多久之前的对象,默认是两周前。

2.12 完整的Git视图

image-20220524213828203.png

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

常用命令

一般使用git push origin master命令即可完成

冲突问题

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

推送规则限制

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

03.Git研发流程

3.1 不同的工作流

image-20220524214900055.png

3.2 集中式工作流

image-20220524224655265.png

3.2.1 集中式工作流-Gerrit

3.3分支管理工作流

image-20220524224906637.png

3.3.1 分支管理工作流-GitFlow

image-20220524225041987.png

3.3.2 分支管理工作流-Github Flow

image-20220524225154750.png

3.3.3 分支管理工作流-Gitlab Flow

image-20220524231939180.png

3.4 代码合并

Fast-Forward

不会产生一个merge节点,合并后保持一个线性历史,如果target分支有了更新,则需要通过rebase操作更新source branch后才可以合入。

image-20220524232734656.png

Three-Way Merge

三方合并,会产生一个新的merge节点

image-20220524232903511.png

3.5 如何选择合适的工作流

image-20220524233005621.png