git底层原理

git

git init:
当执行git init的时候,会初始化代码仓库,在当前目录下面出现一个.git目录
.git
├── HEAD
├── config
├── description
├── hooks
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags
8 directories, 17 files

1. config:
	是仓库的本地配置文件,对应的还有一个全局的配置文件~/.gitconfig
	同时存在,本地的优先生效,如果没有配置本地,则使用全局配置
	全局配置:git config --global -l 会打印出global的配置
	本地配置:git config -l 打印出local的配置

	配置:git config user.name ”shijiapeng“
		git config user.email "shijiapeng@baidu.com"

git add:
可以创建一个文件,写点东西,这里创建了一个hello.txt, 内容是hello world
git add之前目录不会发生变化:
是因为在代码仓库中进行修改,默认是在工作区中的,工作区中的内容并不归git管理,但是git能够识别出来文件是在工作区中(Untracked file)
如果想让git管理,通过git add添加到缓存区中
git add 之后目录发生的变化:
.git
├── HEAD
├── config
├── description
├── hooks
├── index
├── info
│   └── exclude
├── objects
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

	9 directories, 19 files

通过上述对比:发现,多了一个目录和两个文件:index, 3b(目录)  18e512dba79e4c8300dd08aeb37f8e728b8dad
objects:
	git objects用来干什么?
	git objects最主要的有三种
	先看第一种:blob
		git cat-file :
			查看objects文件(这个文件有一连串的字符,比如3d, 18e,由sha1哈希算法生成,可以把文件的内容或者字符串变成一串加密的字符)
			1. 查看这个objects类型:git cat-file -t 3b18e5 -> blob
				blob:在git对象中是用来存储文件内容的
			2. 看这个objects的内容是什么:git cat file -p 3b1825 -> hello world 
			3. 看这个对象的大小是多少:git cat file -s 3b1825 -> 12 文件字符个数 + 1个换行符
	通过git add把一个文件添加到缓存区中,实际上会生成git的对象objects,它存储的是文件的内容,文件的大小,对象的类型,不存储文件名,  将文件的内容、文件的大小和文件的类型通过sha1哈希算法,来生成字符串3b18e5.....当做是文件名,文件的内容是:”blob 12\0 hello world“通过压缩之后的内容
	那么文件名的信息去哪里了?

工作区和缓存区(索引区):
文件名的信息存储在index文件中
查看index文件内容:
git ls-files:把当前在缓存区中的文件给列出来
git ls-files -s:将文件的其他信息也列出来:文件的权限,文件所对应的blob对象, ?, 文件名
索引区:连接工作区和代码仓库,为了代码仓库去做准备,index保存了待提交的当前的状态
Untracked files: 表示没有提交到缓存区中的新创建的文件
Changes to be commited: 表示缓存区中的文件
Changes not staged for commit: 表示修改了索引区中的文件,还没有提交到索引区

当我们先创建了一个文件并且提交了索引区,此时发生的变化:
1. 生成一个index文件,通过git ls-files -s 查看文件信息,里面存储的是待提交的文件当前的状态
2. 在.git/objects下生成了一个以文件内容 + 文件大小 + 文件类型通过hash算法生成一连串字符串为命名的文件

当我们修改了一个已经提交到索引区的文件,此时发生的变化:
1, 更新index待提交的文件的状态,会更新这个文件所对应的blog对象,也就是那一串hash值
2, 在.git/objects下重新生成了一个以文件内容 + 文件大小 + 文件类型通过hash算法生成一连串字符串为命名的文件,之前的保留
.git
├── HEAD
├── config
├── description
├── hooks
├── index
├── info
│   └── exclude
├── objects
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad(hello.txt)
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3(原来hello2.txt)
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f(新的hello2.txt)
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags
11 directories, 21 files

git commit:
git commit 之后的结构发生的变化

├── COMMIT_EDITMSG
├── HEAD(是一个指针,永远指向当前工作的分支)
├── config
├── description
├── hooks
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 19
│   │   └── d20615f940b4ca6c24ed262c7aee5a5ca5e13b(新的, tree对象)
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f
│   ├── f0
│   │   └── c347606fdaa6a9c4c0302b9d9e8664c6410f7b(新的, commit对象)
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master(当前master分支最新的commit是这个master内容f0c347606fdaa6a9c4c0302b9d9e8664c6410f7b,commit对象)
    └── tags

16 directories, 27 files

通过 git cat-file -t foc347:来查看这个对象的类型,发现他是一个commit
这是git objects的第二种类型

通过-p 来查看这个commit对象的内容:
tree 19d20615f940b4ca6c24ed262c7aee5a5ca5e13b
author shijiapeng <shijiapeng@baidu.com> 1625923968 +0800
committer shijiapeng <shijiapeng@baidu.com> 1625923968 +0800

1st commit


git objects的第三种类型:tree,后面是它的文件的名字
作者的信息以及邮件的地址,时间戳

以上就是一个commit对象包含的内容


通过-p观察一下tree对象包含的内容:
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad	hello.txt
100644 blob d02b296954690b63febc7f8869cedcf72e6ab16f	hello2.txt
类似于一个目录的作用,包括这次提交的文件的状态


总结:一次commit,产生了两个对象,一个commit对象,一个tree对象

这里将hello2.txt再次改变
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 19
│   │   └── d20615f940b4ca6c24ed262c7aee5a5ca5e13b
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── 9f
│   │   └── 345dcadd7545224978de6e244d1d486f74e672(新的hello2.txt)
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f
│   ├── f0
│   │   └── c347606fdaa6a9c4c0302b9d9e8664c6410f7b
│   ├── info
│   └── pack
└── refs
    ├── heads
    │   └── master
    └── tags

 第二次commit之后,tree的变化:

├── objects
│   ├── 02
│   │   └── cc618005ead9df31a5474ae72dedc289b31adf(新的commit对象)
│   ├── 19
│   │   └── d20615f940b4ca6c24ed262c7aee5a5ca5e13b
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── 49
│   │   └── c1294c2a600622b6c5cb9969b93053227e3cb7(新的tree对象)
│   ├── 9f
│   │   └── 345dcadd7545224978de6e244d1d486f74e672
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f
│   ├── f0
│   │   └── c347606fdaa6a9c4c0302b9d9e8664c6410f7b
│   ├── info
│   └── pack

新的commit对象内容:
tree 49c1294c2a600622b6c5cb9969b93053227e3cb7
parent f0c347606fdaa6a9c4c0302b9d9e8664c6410f7b
author shijiapeng <shijiapeng@baidu.com> 1625925573 +0800
committer shijiapeng <shijiapeng@baidu.com> 1625925573 +0800

2nd commit

添加文件对于git objects的测试
在工作区中,一个空文件,git认为是不算是改变的,必须在空文件里面添加内容, git add之后
├── objects
│   ├── 02
│   │   └── cc618005ead9df31a5474ae72dedc289b31adf
│   ├── 19
│   │   └── d20615f940b4ca6c24ed262c7aee5a5ca5e13b
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── 49
│   │   └── c1294c2a600622b6c5cb9969b93053227e3cb7
│   ├── 9f
│   │   └── 345dcadd7545224978de6e244d1d486f74e672
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f
│   ├── e3
│   │   └── 69d65e71acc22096ffa85dede56d74147e0295(新的)
│   ├── f0
│   │   └── c347606fdaa6a9c4c0302b9d9e8664c6410f7b
│   ├── info
│   └── pack
也就是说目录不算一个对象

git commit之后
├── objects
│   ├── 02
│   │   └── cc618005ead9df31a5474ae72dedc289b31adf
│   ├── 19
│   │   └── d20615f940b4ca6c24ed262c7aee5a5ca5e13b
│   ├── 3b
│   │   └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│   ├── 49
│   │   └── c1294c2a600622b6c5cb9969b93053227e3cb7
│   ├── 9f
│   │   └── 345dcadd7545224978de6e244d1d486f74e672
│   ├── a2
│   │   └── 34dcc620326f6fbd01dc04abdcd70b454a6af3
│   ├── bc
│   │   └── ff05e62dd58edbd7395e12a8812f66eda63d00(tree:里面包含一个tree)
│   ├── d0
│   │   └── 2b296954690b63febc7f8869cedcf72e6ab16f
│   ├── e3
│   │   └── 69d65e71acc22096ffa85dede56d74147e0295
│   ├── f0
│   │   ├── 5bff8e42b68e6afb67bd0c1a340490864b0b54(commit对象)
│   │   └── c347606fdaa6a9c4c0302b9d9e8664c6410f7b
│   ├── f2
│   │   └── 5733bf4595c604f4096012e095c8ae7f02cf8c(tree对象)
│   ├── info
│   └── pack


一个comiit包含一个tree,这个tree里面又包含一个tree和两个文件

git文件状态:
Untracked:在工作区新创建一个文件的时候,Untracked -> Staged:git add
Modified:文件已经在Staged中,又对这个文件做了一个修改 Modified -> Staged:git add
Staged:在暂存区中的文件
Unmodified:在仓库中的文件:Staged -> Unmodified:git commit

反向切换:


总结:每次git add 的时候 会在.git/objects文件中生成一个blob对象,如果对这个文件修改,会重新生成一个新的blob对象,文件原来的对象保留
	其实这个对象以文件的形式存在,可以通过 git cat-file来查看这个文件,-s:查看这个文件的内容,-s:查看这个文件大小,-t:查看这个文件的类型

	在git add之后,index文件中保存的是暂存区中文件状态信息,包括文件名字,文件的权限,文件对应的对象名字,可以通过git ls-files来查看index
	-s:可以把上述信息都给显示出来

	git commit的时候,会在.git/objects文件中生成两个对象,commit对象和tree对象,commit对象的内容包括,它的父commit对象(也就是上次提交的commit对象),它还包含一个tree对象,其他的就是提交者的信息,tree对象的内容包括:这次提交的文件的状态(和index文件中的内容是一致的)

	对于文件夹的操作:
	在git中空文件夹是不算改变的,文件夹中必须有内容
	example:新创建一个文件夹folder,git status是看不到任何改变,接着在folder下创建一个a.txt,再次git status会发现发生了改变,git add之后发现objcts下只生成了一个对象,这说明文件夹是不算对象的,但是会在index中记录这个a.txt所在的路径

	git commit的时候:会生成三个对象,一个commit, 两个tree,一个tree里面包含另一个tree,另一个tree的内容是a.txt,也就是当创建目录,在目录下对文件进行修改的时候,会通过生成不同的tree对象来记录文件的层次关系,也就是通过tree来表示文件夹层次关系

Branch:
分支是一个有名字的指针,指向一个commit
分支指向的commit存储在哪里?
怎么知道当前在那个分支?.git/HEAD 它是一个指针,总是指向当前工作的分支,并且指向当前工作分支最新的一次commit
实现是这样的:HEAD里面存储的是当前指向那个分支(存储的是这个分支的路径),然后可以查看这个分支的文件,这个分支文件里面存储的sha1计算出来的字符串那,是一个commit对象,就表示这个分支指向的最新一次的commit

git log 可以看到提交的历史

分支操作:
git branch:当前有哪些分支
git branch <branch_name> 创建分支,基于当前分支
git branch -D <branch_name> 强制删除分支
git checkout 切换分支

当我们删除分支后,这个分支产生的特有的对象依旧可以访问到(也就是当前分支上没有文件),这个对象算是垃圾对象,就像是多次git add 已经提交的文件,之前的产生的对象就变成垃圾对象

git branch 创建一个分支,创建分支的背后发生了什么呢?
	objects/下没有任何变化,因为只是创建了一个指针,这个指针的信息保存在:.git/refs/head/下,创建一个分支名为名字的文件
git checkout 切换分支,切换分支实质:就是HEAD内容发生了改变

git branch -D 在当前分支上,无法删除当前分支,-D:是强制删除,--delete:不会强制删除,会提示错误信息,只是删除某个指向特定commit的指针而已,但是这个commit本身并没有别删除

git checkout 某一个指定的commit:
git checkout -b 分支名:创建并切换到这个新创建的分支上
git switch -C 分支名:
前面说到,git中,是HEAD指向分支名的名字,分支指向commit对象,”git checkout 某一个指定的commit“ 这个意思是说让我们的HEAD直接指向一个commit对象,这个状态叫做”分离头指针“,它的一个使用场景是什么呢?
场景:比如我删除了某个分支,(注意:删除的本质只不过是删除了这个分支的名字,并没有删除这个分支commit
对象),但是我可能是误删除,想要找到这个分支并恢复这个分支,那么应该怎么做呢? 首先通过 git reflog找到这个commit对象的sha1字符串,
然后git checkout 这个sha1字符串,此时会出现上述的分离头指针的状态,但是当切换一个已有的分支的时候会丢弃这个状态,想要保存,可以通过新创建并切换到这个分支上的操作,就相当于现在是一个匿名的分支,需要为它起一个名字,可以通过:最开始的两种方法

git diff:
shijiapeng@B000000346147t git-demo % git diff
diff --git a/test.txt b/test.txt
index 9daeafb…9276bb4 100644
— a/test.txt
+++ b/test.txt
@@ -1 +1,3 @@
test
+
+haha

分析上述信息:
a:代表索引区工作内容,b:代表工作区的工作内容
diff --git a/test.txt b/test.txt
index 9daeafb..9276bb4 100644	(9daeafb:索引区中有效的blob对象,9276bb4:工作区中的blob对象,无效 )
--- a/test.txt(--:代表索引区)
+++ b/test.txt (++:代表工作区)
@@ -1 +1,3 @@	(通过@@作为块的划分,来分割一块一块的不同)(-1:在索引区文件的第四行,+1,3表示:会显示工作区第一行,往后数3行的内容)
 test
+

git diff 默认比较的是索引区和工作区的不同
git diff --cached 比较的是索引区和代码仓库的不同

远程仓库:
将本地仓库导入到远程仓库:
1. 添加远程库:git remote add origin 远程仓库地址url
发生的变化:
修改了.git/config文件,配置了一个 remote origin,内容是url 和 fetch
2. 将本地仓库内容添加到远程仓库中去:git push -u origin master:
把本地仓库push到远程origin仓库的master分支
做的事情:将原来的objects对象进行了压缩,并写到远程仓库上,其次就是在远程仓库上创建了一个master分支
发生的变化:多了4个目录,2个文件
res/remotes/origin/master
log/remotes/origin/master
master文件的内容:sha1哈希值,类型是一个commit对象,和当前的本地master指向同一个对象

远程仓库存储:git remote add origin

git对象压缩:
场景:比如我本地有一个文件10MB, 当我进行一次git add的时候,会在.git/objects下生成一个经过压缩的git对象,这个git对象的内容就是本地文件的内容经过压缩之后的内容,比如是7MB,如果对这个文件进行轻微的删除,第二次进行 git add,那么在objects下还会生成一个7MB的git对象,第三次,第四次git add都会产生同样的效果,长远来看,我本地才占10MB,如果我要有很多次提交,那么占用的空间会非常的大

git gc:
	效果,.git/objects/pack

git merge:
fast forward:
场景:在当前master分支上,chekout出了一个bugfix分支,并且进行了commit操作,master分支一直没动,还是当初checkout出bugfix时的分支,然后在master分支上,执行了git merge bugfix的操作,实际上是将master分支的HEAD指针,移动到了bugfix(前天条件是:master没有移动)
在merge之后,在.git下会出现一个ORIG_HEAD文件,这个文件存储是merge之前的当前分支的commit,方便回滚
3 way merge:
场景:基于master分支,checkout出了一个bugfix分支,并且这个bugfix分支进行了commit操作,然后master分支也进行了commit操作,在master执行git merge bugfix,会生成一个新的commit对象,这个commit对象的parent有两个,分别是生成此对象master和bugfix的最新的commit(前提是master移动了)
3 way mrege带冲突:
场景,基于3 way merge的场景下,bugfix和commit都修改了同一个文件,然后在master执行git merge bugfix会产生冲突,此时如果git ls-files -s 会发现在索引区会出现三个文件,我们解决完冲突之后
直接git add, 然后查看git ls-files -s ,就会只有一个,然后直接git commit 就可以了,不用加-m, 也会生成一个新的commit对象,有两个parent,同上
git rebase:
场景:比如master分支和bugfix分支,已经出现分叉了,不想3 way merge,想要fast forwoard合并,那么可以进行rebase
首先,fast forward的前提是:master基于bugfix分支没有动过,换句话说,bugfix分支上含有master分支的最新一次提交
rebase的目的是就是想办法让没有master的最新一次commit的bugfix分支(出现分叉了),重新拥有master的最新一次提交
在bugfix上,执行git rebase master,
存在的问题,会重写commit,如果已经push过commit了,在rebase之后,再次push就会出错
如果别人拉了你的代码,然后你进行了rebase,并且强制push了,那么会对别人产生影响,所以如果分支有多人共同使用,尽量不要rebase
不要在master上rebase,在自己分支上rebase

tag:

本地分支和远程分支:
git branch -r 查看远程分支
git fetch 检查本地分支对应的的远程仓库的分支的情况,并且同步远程仓库,也可以拉去本地没有的远程分支,如果远程分支被删除,那么不会删除本地的远程分支
git fetch --prune: 会对远程仓库没有,但是本地有的分支,进行删除
git remote: 显示远程仓库的名字
git remote -v 显示push或fetch的url,通过这个url来进行fetch或push
git remote show 远程仓库的名字 去检查远程仓库分支和本地分支的关联的情况

	 可以通过git fetch来更新本地的远程分支,但是如果想要更新本地分支,需要git merge origin/远程分支
	 git branch -vv 查看本地分支是否和本地的远程分支关联,以及如果关联,版本之间差异
	 如果和关联本地分支和本地的远程分支?
	 首先通过git fetch,然后git checkout 本地分支名

git fetch & git pull:

FETCH HEAD:
当我们执行git fetch的时候,.git,目录下会出现FETCH_HEAD文件,这个文件中记录了所有远程分支的最新一次的commit的对象,他们的顺序会发生变化
在本地分支和远程分支关联的情况下,如果切换分支后,并且执行git fetch,再次查看.git/FETCH_HEAD会发现远程分支的顺序会发生变化,第一行就是我们当前分支所关联的远程分支,只要执行git fetch就会发生这样的变化
git pull = git fetch + git merge: 会首先将进行git fetch, 更新所有的远程分支到本地,也会更新FETCH_HEAD的最新commit,以及不同分支的位置,然后会git merge FETCH_HEAD中第一个位置的commit

git push:
将本地分支推送到远程仓库,如果是第一次推送到远程仓库,使用:git push origin dev,第一次推送,远程仓库中没有这个分支,那么会在远程仓库中新建一个分支,加上git push -u参数,可以让建立本地分支和新建的远程分支的连接,这样再下次推送远程分支的时候,直接git push就可以了
git push origin -d dev,可以删除远程分支

git branch -vv:
可以查看本地分支和远程分支的追踪情况

.git/hook:

git work tree:
本地有两个分支master 和 bugfix,我在bugfix分支上进行开发,此时需要切换到master上进行操作,但是bugfix上有一些更改的文件还没保存,如果直接checkout master,会把对文件的新的修改,带到master,但是我们不想带到master,有三种办法:
1. 在bugfix分支上进行一次commit
2. 在bugfix分支上进行一次git stash操作,然后master分支上完事之后,切换到bugfix分支上,执行git stash pop操作
3. 在bugfix分支上执行git worktree add ./branch-master master, 这条命令会将master分支保存到当前目录的branch-master目录里面,我们进行cd branch-master就切换到了master分支,执行完操作之后,可以进入到我们bugfix分支对应的目录,通过git worktree list查看,然后通过git worktree remove branch-master 删除这个目录,可以看到我们不用对本地修改的文件做任何的更改,就可以完成其他分支的开发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值