当前位置 : 主页 > 编程语言 > c语言 >

Git 的正确使用姿势与最佳实践

来源:互联网 收集:自由互联 发布时间:2023-08-25
Git 的正确使用姿势与最佳实践 本文并不会具体介绍 git 的很多命令,侧重于深入理解 git 的基本原理,侧重于原理,带你剖析 .git 文件夹下的东西到底是什么 Git 是什么 基本原理 每一个库都

Git 的正确使用姿势与最佳实践

本文并不会具体介绍 git 的很多命令,侧重于深入理解 git 的基本原理,侧重于原理,带你剖析 .git 文件夹下的东西到底是什么

Git 是什么

基本原理

  1. 每一个库都存有完整的提交历史,可以直接在本地进行代码提交
  2. 每次提交记录的都是完整的文件快照,而不是记录增量
  3. 通过 push 等操作来完成和远端代码的同步

优点

  1. 分布式开发,每一个库都是完整的提交流失,支持本地提交,强调个体
  2. 分支管理功能强大,方便团队管理,多人协同开发
  3. 校验和机制保证完整性,一般只添加数据,很少执行删除操作,不容器导致代码丢失

缺点

  1. 相对 SVN 来说更复杂,学习成本更高
  2. 对于大文件的支持不是很好(git-lfs 工具可以弥补这个功能)

Git 的出现就是为了当初为了维护 Linux 开发出现的,和 Linux 是同一个创始人

Git 的发展历史

  • Github 全球最大的代码托管平台

  • GitLab 全球最大的开源代码托管平台,项目的所有代码都是凯源的,便于自己在服务器上部署 GitLab

  • Gerrit 由谷歌开发的一个代码托管平台,Android 就托管在这个平台上,对于多仓库有更好的支持

  • Gitee 国内的代码托管平台

Git 基本使用方式

常见问题

  1. :tada: 为什么明明配置了 Git 配置,但是依然没有办法拉取代码

没有配置密钥,但最初遇到这个问题是在 push 到远端的时候一直无响应,后来发现要配置一下密钥,但是使用 http 方式的话就不需要啦.如果使用的是 ssh 方式无论是拉取还是 push 都要配置密钥

生成/添加SSH公钥 - Gitee.com

Github 也是同样的道理

还有一种可能就是仓库本身设置了权限,总结一下就是,还是权限出了问题,

  1. :cactus: 为什么明明 fetch 了远端分支,但是我看本地当前的分支历史还是没有变化

fetch 只会更新本地记录的 origin 分支,也就是更新本地记录的远端分支到远端仓库的代码

git remote -v # 查看远程分支

初始化

mkdir demo
cd demo

git init
git init -h


    --template <template-directory>
                          directory from which templates will be used
    --bare                create a bare repository
    --shared[=<permissions>]
                          specify that the git repository is to be shared amongst several users
    -q, --quiet           be quiet
    --separate-git-dir <gitdir>
                          separate git dir from working tree
    -b, --initial-branch <name>
                          override the name of the initial branch
    --object-format <hash>
                          specify the hash algorithm to use

条三个比较重要的

  1. --template 参数指定 模版文件,git将会根据这个模版文件创建一个仓库
  2. --bare 创建一个裸仓库,不包含工作区
  3. -b 参数 初始化分支 main 或者 master,当然也可以配置全局默认值

其实有一个比较重要的概念就是 裸仓库 , 其实就是只存在仓库记录本身,不存在实际的工作区. 重要用途就是作为中心仓库供其他人推拉代码的. 这里其实牵扯到 Git 的存储原理,其实简单说就是 Git 会保存每一次提交的文件. 所以,单单只有一个 .git 文件夹也是可以恢复整个仓库的. 那么对于中心仓库来说,也仅仅存储 .git 文件夹下面的东西就好了,没必要再单独的保存一个特定分支的文件.因为每一个提交和分支都已经保存在了 .git 中,下面会介绍 Git 的详细原理

浅谈Git的内部原理

Git 配置

Git 有三个级别的配置

graph TD;
A(全局 global)
B(系统 system)
C(本地 local)
A -->B
B --> C
配置等级 配置文件位置 Global ~/.gitconfig System /etc/gitconfig Local .git/config

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

基本配置
  1. 用户名配置
git config --global user.name "xiaoming"
git config --global user.email "xiaoming@gmail.com"
  1. instead of 配置
git config --global url.git@github.com:.insteadOf https://github.com/

ssh 协议换 http 协议 或者其他协议的更换,注意前后顺序,是前面的替换后面的

  1. Git 命令别名配置
git config --global alias.cin "commit --amend --no-edit"

使用 cin 来替换 commit --amend --no-edit 可以直接使用 git cin

Remote 配置
git remote add origin_ssh git@github.com:Tom-debug110/demo.git
git remote add origin_http https://github.com/Tom-debug110/demo.git

配置好以后,可以打开 .git/config 看到一些东西

cat .git/config

[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true
[remote "origin"]
	url = https://github.com/Tom-debug110/demo.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[remote "origin_ssh"]
	url = git@github.com:Tom-debug110/demo.git
	fetch = +refs/heads/*:refs/remotes/origin_ssh/*

尤其是后面几行,就是 [remote] 相关的配置,甚至我们可以手动更改里面的配置项

其他配置

git remote rename <old> <new> #更改远端名称
git remote remove <name>      #移除某一个远端
git remote -v                 #输出当前所有远端信息

能否设置同一个远端不同的 pushfetch

上面通过 git remote add 默认情况下, fetch push 是同一个源,但是是可以设置不同源的

git remote add origin git@github.com:Tom/project.git
git remote set-url --add --push origin git@github.com:Other/project.git

通过 git remote -v 即可看到效果

image-20230210192456227

接着来看看 .git/config 文件发生了什么变化

image-20230210193120105

尤其是最后一条,相比于其他的 remote 多了一个 pushurl

http 和 ssh

访问远端仓库一般有两种协议,一种是 http ,另外一个就是ssh 无论是从安全性还是访问速度来看,都推荐使用 ssh 的方式来访问仓库,但是一定不能忘记配置公钥到服务器上,而且推荐使用 ed25519

具体方法

生成/添加SSH公钥 - Gitee.com

然后在 Github Gitee 平台中的个人账户设置中配置即可,如果要配置多个不同的密钥,参考以下:

Git配置多个SSH-Key - Gitee.com

Git Add

这里关于暂存区和工作区的介绍不再赘述

执行git add 前后的区别

image-20230210195752978

image-20230210195922005

<font color=pink>注意看线框中的部分</font>

多出来的这个东西,应该连起来才有意义,可以使用

git cat-file -p e69de29bb.....5319

输出的就是刚才我们README.md 中添加的内容

上面截图的时候忘记使用 vim 打开 README.md 文档添加内容啦,如果有读者读到,还请自行添加一些内容到 README.md 文档,否则将没有内容输出

Hello World
Hello World

经过 git add . 操作,已经把文件添加到了暂存区

Git Commit

git commit -m 'add files'

执行命令后,发现

image-20230210201532260

相比于 git add . 之后多了两个这些东西(暂时叫东西吧)

下面来看一下都是何方圣神

git cat-file -p 4690 #其实不输入完整也是可以的哈

# 输出
100644 blob 8254f875c6f8d41f9137917a46804bc5eb9e781f	README.md

后面不就是我们的文件名吗,其实,上面的意思表示这个玩意是一个目录树类型的 object

再看一下另外一个

git cat-file -p b4e2af

#输出
tree 4690e50c687a1e77c71bb5461ee3e4357df8d20c
author mixinju <mixinju@outlook.com> 1676031166 +0800
committer mixinju <mixinju@outlook.com> 1676031166 +0800

add file

这不就是刚才的提交信息吗. 先看第一行, tree指明了目录树,后面的一串是不是很眼熟呢?不就是指明了目录树的校验和值吗

第二行指明了作者,第三行指明了提交者,大部分情况下作者就是提交者,但也有特殊情况吧

git log

# 输出
commit b4e2af8233708a5dcb763f4dde699208d773af40 (HEAD -> main)
Author: mixinju <mixinju@outlook.com>
Date:   Fri Feb 10 20:12:46 2023 +0800

    add file
(END)

出现的b4e2af8233708a5dcb763f4dde699208d773af40 不就是.git/object/ 下其中一个吗

:tangerine: **Git 中的 Object **

commit tree blob 在 Git 里面都统称为 Object ,除此之外还有一个 tagObject

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

除此之外,还有一个叫做 TagObject,下面说到Git tag 的时候会介绍到

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

  1. 通过 commit 找到 tree 信息,每一个 commit 对应着一个 tree的 id

  2. 通过 tree存储的信息,获取到对应的 目录树的信息(tree 中可能存有多个 blob)

  3. tree 中获取 blob 信息

image-20230210204122462

➜  demo git:(main) tree .git/objects
.git/objects
├── 46
│   └── 90e50c687a1e77c71bb5461ee3e4357df8d20c
├── 82
│   └── 54f875c6f8d41f9137917a46804bc5eb9e781f
├── b4
│   └── e2af8233708a5dcb763f4dde699208d773af40
├── info
└── pack

5 directories, 3 files
➜  demo git:(main) git cat-file -p b4e2
tree 4690e50c687a1e77c71bb5461ee3e4357df8d20c
author mixinju <mixinju@outlook.com> 1676031166 +0800
committer mixinju <mixinju@outlook.com> 1676031166 +0800

add file
➜  demo git:(main) git cat-file -p 4690
100644 blob 8254f875c6f8d41f9137917a46804bc5eb9e781f	README.md
➜  demo git:(main) git cat-file -p 8254
Hello World
Hello World
➜  demo git:(main)

:warning: 一个 tree Objects 可能含有多个 blob 比如下面:

image-20230210204750115

Git Refs

Branch

先简单说一下分支吧

git branch dev1 						# 基于当前分支创建一个 dev1 的分支,但是不切换过去,还需要手动切换
git branch -a   						# 列出远端和本地的所有分支
git branch -d  dev1 				# 删除 dev1 分支
git branch -m  dev1 dev2 		# 重命名dev1 分支为 dev2


git checkout -b dev2 		# 创建一个 dev2 分支并切换过去
git switch -c dev2      # 创建一个 dev2 分支并且切换过去

git checkout dev2       # 从当前分支切换到 dev2 分支
git switch dev2         # 从当前分支切换到 dev2 分支

对于分支切换和创建都建议使用 git switch 命令

Ref

接下来看一下 .git/refs 文件夹里面的内容

tree .git/refs

.git/refs
├── heads
│   └── main
└── tags

然后查看一下这个 main 里面保存着什么

cat .git/refs/heads/main

b4e2af8233708a5dcb763f4dde699208d773af40

接着继续查看这个 校验和表示的内容

cat-file -p b4e2af8233708

#输出
tree 4690e50c687a1e77c71bb5461ee3e4357df8d20c
author mixinju <mixinju@outlook.com> 1676031166 +0800
committer mixinju <mixinju@outlook.com> 1676031166 +0800

add file

会发现,这不就是刚才的那个提交吗?

现在新建一个分支( branch )来看看

git switch -c test
Switched to a new branch 'test'


tree .git/refs
.git/refs
├── heads
│   ├── main
│   └── test
└── tags

2 directories, 2 files

会发现在 .git/refs/heads/ 目录下多了一个 test 条目,接下来看一下这个 test 里面存储的什么

.git/refs/heads/test

b4e2af8233708a5dcb763f4dde699208d773af40

会发现和上面的 main 是相同的校验值,也就是同一个 commit ,因为我们的 test 分支就是从 main 分支切换过来的,而且目前还没有在 test 分支上做任何的事情,自然就和 .main 分支一样,指向了同一个分支

Tag

标签一般表示一个稳定版本,指向的 commit 一般不会变更

git tag v1.0.0. # 创建一个 tag

来查看一下 .git/refs 中的变化

tag v1.0.0
tree .git/refs
.git/refs
├── heads
│   ├── main
│   └── test
└── tags
    └── v1.0.0

2 directories, 3 files

可以看到 .git/refs/tags 下多出来的一个文件(校验值)

cat .git/refs/tags/v1.0.0
b4e2af8233708a5dcb763f4dde699208d773af40

还是 main 分支上的最后一个 commit

Annotation Tag

这个是附注标签,可以给 tag 添加一些额外的信息

git tag -a v2.0.0 -m "add feature1"

使用 -m 选项来添加附注,继续查看 .git/refs/tags 下变化

git tag -a v2.0.0 -m "add feature1"
tree .git/refs/tags

.git/refs/tags
├── v1.0.0
└── v2.0.0

0 directories, 2 files

cat .git/refs/tags/v2.0.0
b63b9bc0d5c2aa210791266f20f5386eb50178de

这个时候我们会发现,此时的这个校验值已经不再是最新的那个 commit 啦. 查看 .git/objects 会发现新增了一个文件(夹)

image-20230211112422576

使用 git cat-file 查看这个 id

image-20230211112621823

这个和前面提到的那个 tag Object 就对应上了

总结一下

  1. refs 文件存储的内容就是对应的 commit id 可以把 ref 当作一个指针,指向对应的。commit 来表示当前 ref 对应的版本

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

追溯历史代码

:baby_bottle: 获取当前版本代码

可以通过 ref 指向的 commit 可以获取唯一的代码版本

:game_die: 获取历史版本代码

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

我们上面没有看到这个字段是因为没有进行连续的多个提交,就是没有产生迭代,自然也就没有 parent 只说啦

echo "Hello world KKKKKKKKKKK" > README.md
git add .
git commit -m 'add kkkkk'

查看 .git/objects 下的文件

image-20230211113721997

新增了三个 object 其实这三个 object 分别是一个 commit 存储提交信息,一个 blob 存储文件信息,一个 tree 存储目录树信息

然后查看 commit ,到底哪一个是 commit 呢,使用 git log 来查看一下

image-20230211114017756

q 退出即可

查看这个 commit 对应的信息

image-20230211114228193

注意看,这里就出现了 parent 字段,而且的确是上一次 commitid ,同时,main 分支也更新到了最新的 commit ,在 .git/refs/heads

下,查看 main 分支当前的指向

image-20230211114714502

修改历史版本

  • :lantern: commit --amend

通过这个命令来修改最近一次提交的 commit 信息,<font color=pink>修改之后的 commit id 会发生变化</font>,具体的不再通过查看 id 的形式演示了,就给一个图示吧

graph TD;
A((commit1))
B((commit2))
C((commit3))
D(commit4 add file)
E(修改commit4 提交信息)
F((commit5))
A -->B
B -->C
C -.废弃.->D
C -->E
E -->F


其中的 commit4 也被称为悬空的 Object 可以使用 git fsck --lost-found 来查找这个悬空的 commit

git fsck --lost-found

Checking object directories: 100% (256/256), done.
dangling commit f209eb9c50dbb6efe0d5c545dd741f565ab130fd
  • :ear_of_rice: rebase

先学习一下使用吧,下面都是比较权威、清晰的讲解,建议先看文档,再看视频.

Git - 变基 (git-scm.com)

git rebase: 人生无法重来,但代码可以!

建议实际操作一下,就会对这两个玩意有更清楚的认识啦

总结

git rebase 是非常强大的命令,要搞懂它的基本使用,注意一点就是不要对还有他人引用进行 rebase 操作

git rebase的时候捅娄子了,怎么办?在线等…… - 掘金 (juejin.cn)

上一篇:MFC练习4:自动关机程序(恶搞版)
下一篇:没有了
网友评论