【Git】从git的目录出发学习git

154 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

1.重点

  • Git Add

将代码从工作区提交到暂存区

  • Git Commit

将暂存区代码提交到 Git 存储

本质上都是在.git目录下面

image.png

origin_img_v2_636c5838-80d8-4a9b-a0d9-be35873ba64g.jpg

帮助命令 git <命令> -h

2.命令详解

2.1 Git init

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

2.2 Git Config

Git 配置,分成本地,用户,系统基本的配置

  • 用户名配置

    • git config --global user.name“aaa"
    • git config --global user.email aaa@qq.com
  • lnstead of配置

  • Git命令别名配置(简化命令输入)

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

2.3 Git Remote

Git Remote 配置,分成 SSH 和 HTTP 两种协议实现,不同协议有不同的免密配置方式

本质上修改也是加到.git/config

查看Remote git remote -v

添加 Remote git remote add origin_ssh git@github.com:username/res.git git remote add origin_http github.com/username/re…

业务场景:从开源地址上fetch,push到本地仓库 git remote set-url --add --push origin git@github.com:me/my_repo.git

2.3.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”
不指定目录的情况默认是~l.git-credentials

将密钥信息存在指定文件中 具体格式:${scheme}://${user}:${password}@github.com

2.3.2 SSH Remote

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

目前的 Key的类型四种,分别是dsa、rsa、ecdsa、ed25519

默认使用的是 rsa,由于一些安全问题,现在已经不推荐使用 dsa和rsa了,一些新版本的windows代码不允许这两种key访问优先推荐使用ed25519,

ssh-keygen -t ed25519 -C“ your_email@example.com”

密钥默认存在~/.ssh/id_ed25519.pub

2.4 Git add

把所有文件加入暂存区

git add . 

可以显示出与这个id对应的文件内容

git cat-file -p <object的id>

观察下列命令信息

PS F:\CodeTest\gittest> tree .git
卷 Games 的文件夹 PATH 列表
卷序列号为 0000002D 701A:1B2D
├─hooks
├─info
├─objects
│  ├─info
│  └─pack
└─refs
    ├─heads
    └─tags
    
PS F:\CodeTest\gittest> git add .
PS F:\CodeTest\gittest> tree .git
卷 Games 的文件夹 PATH 列表
卷序列号为 000000C5 701A:1B2D
F:\CODETEST\GITTEST\.GIT
├─hooks
├─info
├─objects
│  ├─95   #这一行多了一个95,里面有个二进制文件,目录名和文件名拼上就是object ID
│  ├─info
│  └─pack
    ├─heads
    └─tags
    
PS F:\CodeTest\gittest> git cat-file -p 95d09f2b10159347eece71399a7e2e907ea3df4f
hello world

2.5 Git commit

提交命令:

git commit -m "提交信息"

观察下列命令信息

PS F:\CodeTest\gittest> git commit -m "add readme.md"
[master (root-commit) 3442425] add readme.md
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
PS F:\CodeTest\gittest> tree .git
卷 Games 的文件夹 PATH 列表
卷序列号为 0000008D 701A:1B2D
F:\CODETEST\GITTEST\.GIT
├─hooks
├─info
├─logs
│  └─refs
│      └─heads
├─objects
│  ├─34
│  ├─5b
│  ├─95
│  ├─info
│  └─pack
└─refs
    ├─heads
    └─tags
PS F:\CodeTest\gittest> git cat-file -p 5b7539b9f8bae66ecebb56f93d7380c323240374
100644 blob 95d09f2b10159347eece71399a7e2e907ea3df4f    README.md
# 表示类型blob 对象id 文件名

PS F:\CodeTest\gittest> git cat-file -p 3442425921780f9a06ec1b68b4c08bd7f74b0b9e
tree 5b7539b9f8bae66ecebb56f93d7380c323240374    #目录树
author user_name <aaa@qq.com> 1653399877 +0800   #作者 后面是时间戳和时区
committer user_name <aaa@qq.com> 1653399877 +0800   #提交者

add readme.md

2.6 Objects

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

Blob 存储文件的内容 Tree 存储文件的目录信息 Commit 存储提交信息,一个 Commit可以对应唯一版本的代码

串联方式:

  1. 通过Commit找到Tree,每个Commit都会存储对应Tree ID
  2. 通过Tree获取目录树信息(一个commit可能存在多个目录下,因此需要),并获取Blob ID
  3. 通过Blob ID寻找对应内容

2.7 Refs

references顾名思义引用,里面存储的都是引用内容

显示master文件存储的内容:

PS F:\CodeTest\gittest> cat .git/refs/heads/master
3442425921780f9a06ec1b68b4c08bd7f74b0b9e  

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

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

2.8 Tag

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

git tag v0.1

# 显示tag内容
PS F:\CodeTest\gittest> cat .git/refs/tags/v0.1
3442425921780f9a06ec1b68b4c08bd7f74b0b9e   #直接显示本次commit的引用

2.8.1 Annotation Tag 附注标签

使用git tag -a <tag> -m <信息> 加附注信息

PS F:\CodeTest\gittest> git tag -a v0.2 -m "这是0.2版本"
PS F:\CodeTest\gittest> cat .git/refs/tags/v0.2
4f2afb5e832a1b95aaf46e5401f267273e90a074      #未出现过的Object,证明附注tag本身创建了Object
PS F:\CodeTest\gittest> git cat-file -p 4f2afb5e832a1b95aaf46e5401f267273e90a074
object 3442425921780f9a06ec1b68b4c08bd7f74b0b9e
type commit
tag v0.2
tagger user_name <aaa@qq.com> 1653401209 +0800

这是0.2版本

2.9 追溯历史版本

修改内容再次执行了一次add和commit过后

PS F:\CodeTest\gittest> git log
commit b7dcf9471395f03e4d32492b4621f179ea93ed92 (HEAD -> test)
Author: user_name <a@qq.com>
Date:   Tue May 24 22:21:48 2022 +0800

    update readme

commit 3442425921780f9a06ec1b68b4c08bd7f74b0b9e (tag: v0.2, tag: v0.1, master) #右边是源操作分支,左边是目的操作分支
Author: user_name <a@qq.com>
Date:   Tue May 24 21:44:37 2022 +0800

    add readme.md
    
    
PS F:\CodeTest\gittest> git cat-file -p b7dcf9471395f03e4d32492b4621f179ea93ed92
tree 9501f0d7aa1e830233eb4e08ffa594eaa8020840
parent 3442425921780f9a06ec1b68b4c08bd7f74b0b9e
author user_name <a@qq.com> 1653402108 +0800
committer user_name <a@qq.com> 1653402108 +0800

update readme


PS F:\CodeTest\gittest> git cat-file -p 9501f0d7aa1e830233eb4e08ffa594eaa8020840
100644 blob 73d17dc60c5d4d929e1c4361bcb554fe3b6d455c    README.md

PS F:\CodeTest\gittest> git cat-file -p 73d17dc60c5d4d929e1c4361bcb554fe3b6d455c
# hello world

2.10 修改历史版本

commit --amend
通过这个命令可以修改最近的一次 commit 信息,修改之后commit id会变

rebase
通过git rebase -i HEAD~3可以实现对最近三个commit 的修改:
1.合并commit
2.修改具体的 commit message
3.删除某个commit

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

2.11 悬空Objects

前面修改commit信息过后,老commit未被删除,成为悬空的Objects 可以通过

git fsck --lost-found

查找悬空Objects

2.12 Git GC (Garbage Collection)

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

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

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

实操:

PS F:\CodeTest\gittest> git reflog expire --expire=now --all

PS F:\CodeTest\gittest> git gc --prune=now
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (7/7), done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0

2.13 完整的git视图

image.png

2.14 Git Clone & Pull & Fetch

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

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

Pull
拉取远端某分支,并和本地代码进行合并,操作等同于 git fetch + git merge,也可以通过git pull --rebase完成 git fetch + git rebase 操作。 可能存在冲突,需要解决冲突。

2.15 Git Push

常用命令 一般使用git push origin master命令即可完成 格式:`git push <源仓库名字> <远程分支名>

冲突问题:

  1. 如果本地的commit记录和远端的commit 历史不一致,则会产生冲突,比如 git commit --amend修改信息导致commit ID变化 or git rebase
  2. 如果该分支确认过可以修改历史则可以通过git push origin master -f来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。

推送规则限制 可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。

2.16 git cherry-pick

git cherry-pick <分支ID号> 把某一次提交内容应用到当前分支

2.17 FAQs

  1. 为什么我明明配置了Git配置,但是依然没有办法拉取代码?
  • 免密认证没有配。
  • Instead Of 配置没有配,配的SSH 免密配置,但是使用的还是HTTP协议访问。
  1. 为什么我Fetch了远端分支,但是我看本地当前的分支历史还是没有变化?
  • Fetch 会把代码拉取到本地的远端分支,但是并不会合并到当前分支,所以当前分支历史没有变化。
  • fetch只会更新origin的分支

3.研发流程

FAQs 1.在 Gerrit平台上使用Merge的方式合入代码 2.不了解保护分支,Code Review,Cl等概念,研发流程不规范 3.代码历史混乱,代码合并方式不清晰

3.1 集中式工作流-Gerrit

  • Gerrit 平台 围绕主分支master进行开发,无其他分支,合入方式Fast-forward

工作方式:

  1. 获取远端master代码
  2. 直接在master 分支完成修改
  3. 提交前拉取最新的master 代码和本地代码进行合并(使用rebase),如果有冲突需要解决冲突
  4. 提交本地代码到master

基本原理:

  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.2 分支管理工作流

  • Git Flow 分支类型丰富,规范严格
  • Github Flow 只有主干分支和开发分支,规则简单
  • Gitlab Flow 在主干分支和开发分支之上构建环境分支,版本分支,满足不同发布or环境的需要 有不同分支,完成开发后再通过MR/PR(Pull Request)合入主干分支,合入方式自定义,Fast-Forward orThree-Way Merge都可以

3.2.1 GitFlow

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

3.2.2 Github Flow

选择团队合作的方式

  1. owner创建好仓库后,其他用户在fork的仓库上开发
  2. owner创建好仓库后,统一给团队内成员分配权限,直接在同一个仓库内进行开发

可以设置保护分支

3.2.3 Gitlab Flow

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

只有在上游分支采纳的代码才可以进入到下游分支,一般上游分支就是Master

image.png

image.png

3.3 代码合并

3.3.1 Fast-Forward

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

git merge <要合并过来的节点> --ff-only

image.png

3.3.2 Three-Way Merge

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

image.png

3.4 选择合适的工作流

针对小型团队合作,推荐使用Github工作流即可

  1. 尽量保证少量多次,最好不要一次性提交上千行代码
  2. 提交Pull Request后最少需要保证有CR后再合入
  3. 主千分支尽量保持整洁,使用fast-forward合入方式,合入前进行rebase

大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中。

3.5 FAQs

  • 在 Gerrit平台上使用Merge的方式合入代码。
    • Gerrit是集中式工作流,不推荐使用Merge方式合入代码,应该是在主干分支开发后,直接 Push,
  • 不了解保护分支,Code Review,Cl等概念,研发流程不规范。
    • 保护分支:防止用户直接向主干分支提交代码,必须通过P阳R来进行合入。
    • Code Review,CI:都是在合入前的检查策略,Code Review是人工进行检查,CI则是通过一些定制化的脚本来进行一些校验。
  • 代码历史混乱,代码合并方式不清晰。
    • 不理解Fast Forward和Three Way Merge 的区别,本地代码更新频繁的使用Three Way 的方式,导致生成过多的Merge节点,使提交历史变得复杂不清晰。