在这里插入图片描述
git clone
克隆远程资源到本地目录,作为工作目录;git pull
就可以更新本地的文件;git status
查看修改的文件。然后使用git add
添加修改的文件暂到缓冲区;git commit
添加到当前的工作区;git push
将本地的修改推送到远程的git服务器。每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。这种情形下通常会有个代表“官方”项目的权威的仓库。
要为这个项目做贡献,你需要从该项目克隆出一个自己的公开仓库,然后将自己的修改推送上去。
接着你可以请求官方仓库的维护者拉取更新合并到主项目。
维护者可以将你的仓库作为远程仓库添加进来,在本地测试你的变更,将其合并入他们的分支并推送回官方仓库。
在这里插入图片描述
!! 当然你也可以像使用SVN一样使用一个中心仓库来交换信息(在项目人员较少时常用这种方式),但是这种工作方式和SVN也不尽全部相同,它们的区别在于,使用git进行开发的人员,每一个节点(每一个电脑上的本地仓库)都拥有完整的仓库,在失去与中心仓库的连接时也可以工作(先提交到本地仓库)。
在git中以存储键值对(key-value)的方式来存储文件。
它允许插入任意类型的内容,并会返回一个键值,通过该键值可以在任何时候再取出该内容。
git的kv中value一般有以下几种类型blob
、tree
、commit
。
在调用git add后会生成一个blob对象,然后将该对象add放进进index区。
首先,我们需要初始化一个新的 Git 版本库
$ git init
Initialized empty Git repository in D:/GitTest/.git/
然后我们查看一下生成的目录结构
$ find .git/ -type d # 查看.git/下的所有目录
.git/
.git/hooks
.git/info
.git/objects
.git/objects/info
.git/objects/pack
.git/refs
.git/refs/heads
.git/refs/tags
git中所有生成的对象都存储在objects中,因为我们研究git存储原理,所以下来着重观察这一目录。
$ find .git/objects/ -type f # 查看.git/objects下的所有文件
我们发现这个目录下是空的,可以通过底层命令 git hash-object
将任意数据保存于.git/objects
目录(即对象数据库),并返回指向该数据对象的唯一的键(kv)。
$ echo 'first add file'>readme.md
$ git hash-object -w readme.md
warning: LF will be replaced by CRLF in readme.md.
The file will have its original line endings in your working directory.
6a0b867bdc470c582c15906f264b9fec371cdbfc
## 这警告是因为换行符在不同系统中格式不一样,为了分布式开发,会自适应系统转换
git hash-object
会接受你传给它的东西,而它只会返回可以存储在 Git 仓库中的唯一键。
-w
选项会指示该命令不要只返回键,还要将该对象写入数据库中。此命令输出一个长度为 40 个字符的校验和。
这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。
我们再次查看objects目录
$ find .git/objects/ -type f
.git/objects/6a/0b867bdc470c582c15906f264b9fec371cdbfc
这就是开始时 Git 存储内容的方式——一个文件对应一条内容, 以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。
校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。可以看到它的hash值就是我们之前生成的值。
值得一提的是只要内容没变那么hash值也不会变,也就是说不会生成新的对象。
我们可以使用giit cat-file
查看该目录下的对象,-t
选项表示查看对象类型,-p
表示查看对象内容。
$ git cat-file -t 6a0b867bdc470c5 #可以只使用部分前缀,只要确能找到唯一对象即可。
blob
$ git cat-file -p 6a0b867bdc470c5
first add file
我们使用git status
可以查看index区的状态,可以很明显的看到我们的文件是红色的,表示文件目前未被加入index区。
我们接下来调用git update-index
命令将该文件加入index区,因为第一次添加该文件我们要使用--add
选项
$ git update-index --add readme.md
我们再次使用git status
查看index区状态,发现readme.md已经变成绿色,表示我们已经成功添加到了index区。
当然我们可以直接调用git update-index \--add readme.md
,而不需要生成手动生成blob对象,但是它的底层原理就是会生成一个blob对象,我们这里只是为了演示这个过程,让大家方便理解。
进行代码提交时,需要根据暂存区的内容,先生成tree
对象,再生成commit
对象,然后会将记录记录到logs
文件夹下。
tree
对象可以通过 git write-tree
命令将暂存区内容写入一个树对象。
$ git write-tree
d8d965c56c04e851e3b47f524c6c52a24c396857
那么tree
对象到底是什么呢,我们使用git cat-file
查看该内容
$ git cat-file -p d8d965c56c0
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc readme.md
100644 表示该文件类型为文本文件。
blob 表示该对象类型。
6a0b867bdc470c582c15906f264b9fec371cdbfc 表示该文件的 hash值。
可以看到和之前我们写入时的值是一样的。
我们添加一个目录,然后将他加入index区,再生成新的tree
对象,来查看目录结构再tree
中如何表示。
$ mkdir -p com/git #递归生成目录
$ echo 'add new file and dirs'>com/git/test.txt
$ git add -A #将工作区所有改变添加到index区
$ git write-tree
956902378ddcc3a768459a852253d1c69bb3a21e
$ git cat-file -p 956902378ddcc3a76
040000 tree 108f37e7bececc2fd72e90d34e9e75ef49b65265 com
100644 blob 6a0b867bdc470c582c15906f264b9fec371cdbfc readme.md
我们可以发现目录的文件类型是040000 ,他在objects中用tree对象表示,那么结构已经很明显了。
如果我们在去查看com的tree对象会发现里面存储了java的tree对象,再去查看会发现test.txt的blob对象。
commit
对象可以通过调用 commit-tree
命令创建一个commit
对象,为此需要指定一个树对象的 SHA-1 值,以及该提交的父提交对象(如果有的话)。
我们从之前创建的第一个树对象开始:
$ echo 'first commit' | git commit-tree d8d965c56c04e8
f742a0cde276d89f79505a500071b6e2577dda45
由于创建时间和作者数据不同,你会得到一个不同的散列值。我们查看commit
对象
$ git cat-file -p f742a0cde276d
tree d8d965c56c04e851e3b47f524c6c52a24c396857
author jyx <xxxxxx@163.com> 1586695870 +0800
committer jyx <xxxx@163.com> 1586695870 +0800
提交对象的格式很简单:它先指定一个顶层树对象,代表此次提交点的项目快照;然后是可能存在的父提交(即上一次提交,前面描述的提交对象并不存在任何父提交);
之后是作者/提交者信息(依据你的 user.name 和 user.email 配置来设定,外加一个时间戳);
留空一行,最后是提交注释。
接着,我们将创建另一个提交对象,它们引用上一个提交(作为其父提交对象):
$ echo 'second commit' | git commit-tree 956902378ddcc3a7 -p 9fc68968003d
3d87a34c5c890d15a3c9eacf935d65085621694d
我们使用git log <commitid>
就可以看到一个提交历史了。
$ git log --stat 3d87a34c5
在这里插入图片描述
那么一个commit
对象可以用下图表示
在这里插入图片描述
git branch
/git tag
原理我们所有的对象都已经介绍完毕了,那么branch
和tag
是如何实现的呢,聪明的你是否已经想到,没错就是使用指针。
因为我们的项目目前还没有任何分支,所以首先我们基于提交点创造一个分支。(在新项目第一次提交时会默认创建一个master分支)
## 基于commit对象创建分支
$ git branch master 3d87a34c5c890d15a3c9eacf935d65085621694d
## 基于分支创建分支
$ git branch dev master
## 基于分支创建tag
$ git tag tag_from_master master
## 基于tag创建分支
$ git branch branch_from_tag tag_from_master
## 等等
使用git branch
查看分支信息
$ git branch
branch_from_tag
dev
* master
使用git tag
查看标签
$ git tag
tag_from_master
创建分支后,我们发现.git/refs/heads/
下多了一些文件
在这里插入图片描述
可以看到这些文件就是我们之前创建的三个分支名称,我们查看这三个文件内容。
在这里插入图片描述
我们在查看tag文件,tag文件存储在.git/refs/tags/
下
在这里插入图片描述
至此我们明白了,所谓的branch和tag都是基于commit对象生成的,他们指向了某一个提交对象。
每一个分支上都一个head指针,表示当前分支的最新提交点。
我们的.git
文件夹下有个HEAD文件。我们打开它查看内容,然后使用git checkout
切换分支,在查看内容
在这里插入图片描述
我们发现了分支的切换实际是改变总的HEAD指针指向.git/refs/heads
下的某个分支文件。
我们在使用git reset时,实际上有一步操作就是将HEAD指针指向我们要回滚的提交点(可以是tag/branch/commit,实际上都是commit对象)。
我们在知道了这些原理后即使我们有时失误使用--hard
进行回滚,我们还可以找到以前的commit对象,来让版本在恢复到回滚之前。
在这里插入图片描述
在这个图中,我们可以看到部分 Git 命令是如何影响工作区和暂存区(stage, index)的。
master
的是master
分支所代表的目录树。HEAD
实际是指向 master 分支的一个指针。所以图示的命令中出现 HEAD
的地方可以用 master
来替换。objects
标识的区域为 Git 的对象库,实际位于 .git/objects"
目录下。git reset HEAD
命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。git rm --cached <file>
命令时,会直接从暂存区删除文件,工作区则不做出改变。git checkout
或者git checkout -- <file>
命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。git checkout HEAD
或者 git checkout HEAD <file>
命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改 动。!! 了解了git的底层原理那么在使用git时就会更加顺手,把他当成一个服务于你的工具。本文参考于 git官方文档。
源于:blog.csdn.net/weixin_42762133/article/details/105458252