git工作原理
1.概念
git就是一个版本管理器,它是分布式的,不仅有一个中心的服务器控制最新版本代码,而且每个开发者自己还有个本地仓库,所以在开发过程中都是先将代码提交到本地仓库再推送到中心服务器上的,这样的好处就是每个人都依赖于中心服务器来实现交互,但又不会被中心服务器限制,就算中心服务器挂了,也能很容易的找到最新版本的代码,而且自己的工作依然可以顺利进行,提交到本地仓库,当中心服务器修复之后,再将自己仓库的东西推送到中心服务器。
2. Git的分层结构
git的工作总共分四层,其中三层是在自己本地也就是前面说的git仓库,包括了工作目录,暂存区和本地仓库。
工作目录就是我们执行命令git init时所在的地方,也就是我们执行一切文件操作的地方,暂存区和本地仓库都是在.git目录,因为它们只是用来存数据的。远程仓库在中心服务器,也就是我们做好工作之后推送到远程仓库,或者从远程仓库更新下来最新代码到我们的git仓库。
git所存储的都是一系列的文件快照,然后git来跟踪这些文件快照,发现哪个文件快照有变化它就会提示你需要添加到暂存区或是提交到本地仓库来保证你的工作目录是干净的。如下图:
根据上图来分析各层的关系:
工作目录(workspace):git add 把改动提交到Index
暂存区(Index):git commit 把改动提交到Respository
本地仓库(Repository):git push 提交到Remotes
git checkout 把分支切到workspace
远程仓库(Remotes):git clone 克隆到Repository
git fetch 把数据拉到Respository
3. Git仓库
当你在一个新目录或已有目录内执行 git init ,这个时候在该目录下会产生一个.git的隐藏文件夹,而该目录就是工作目录,所有操作都是在这个目录里,而这个.git文件夹就是你的本地仓库,当你对一些文件操作之后进行提交,那么首先你就是提交到本地仓库也就是这个.git中,然后再推送到中心服务器。如果你要备份或复制一个库,基本上将这一目录拷贝至其他地方就可以了。目录结构如下:
$ ls
HEAD #这个git项目当前指向哪个分支;
config #项目的配置信息,git config命令会改动它;
description #项目的描述信息
hooks/ #系统默认钩子脚本目录
index #索引文件
logs/ #各个refs的历史信息
objects/ # Git本地仓库的所有对象 (commits, trees, blobs, tags);
info #保存一份排队在.gitignore文件中管理的忽略模式文件
refs/ #标识你项目里的每个分支指向了哪个提交(commit)
4. Git 对象
在Git系统中有四种类型的对象,几乎所有Git操作都是在这四种对象上进行的,这四种对象是:blob、tree、commit、tag。
假如有一个项目,如果我们把它提交(commit)到一个Git仓库中, 在Git中“blob”、“commit”和“tree”对象的关系看起来会如下图:
4.1.blob 对象
blob通常用来存储文件的内容。一个“blob”对象就是一块二进制数据,它没有指向任何东西或有任何其它属性,甚至没有文件名。因为“blob”对象内容全部都是数据,所以两个文件在一个目录树或是一个版本仓库中有同样的数据内容,那么它们将会共享同一个“blob”对象。“blob”对象和其所对应的文件所在路径、文件名是否改被更改都完全没有关系。
4.2 . tree (树) 对象
一个tree对象包括一串条目,每一个条目包括:mode、对象类型、对象名和文件名字。它用来表示一个目录树的内容。
一个tree对象可以指向一个包含文件内容的blob对象,也可以是包含某个子目录内容的其它tree对象。“tree”对象、“blob”对象和其它所有的对象一样,都用其内容的SHA1哈希值来命名的;只有当两个tree对象的内容完全相同(包括其所指向所有子对象)时,它的名字才会一样。这样就能让Git仅仅通过比较两个相关的tree对象的名字是否相同,来快速的判断其内容是否不同。从概念上来讲,Git 保存的数据如图所示。
4.3.commit (提交) 对象
commit对象是存储一次提交的信息,它包括所在的tree,提交时间、作者、指向上次提交的指针等等。
commit主要部分:
- tree: tree对象的SHA1签名,代表目录在某一个时间点的内容;
- parent: 父对象,提交的SHA1签名代表提交前一步的项目历史,合并的提交(merge commits)可能会有不只一个父对象。如果一个提交没有父对象,那么我们就叫它“根提交”(root commit),它就代表着项目最初的一个版 本(revision)。每个项目必须有至少有一个“根提交”(root commit);
- author:作者,做了此次修改的人的名字、修改时间;
- committer:实际创建提交人的名字、提交时间,可能会和作者不是同一个人;
- annotation:注释,用来描述此次提交。
一般用git commit来创建一个提交(commit), 这个提交的父对象一般是当前分支(current HEAD),同时把存储在当前索引(index)的内容全部提交。
运行git add 和 git commit 命令时 Git 进行的工作:
a) 保存修改了的文件的 blob
b) 更新索引
c) 创建 tree 对象
d) 创建 commit 对象
以上这三类 Git 对象: blob,tree以及commit,都各自以文件的方式保存在.git/objects目录下。
4.4.Tag对象
tag对象包括一个对象名(SHA1签名)、对象类型、标签名、标签创建人的名字, 还有一条可能包含有签名(signature)的消息。Tag 对象非常像一个 commit 对象,包含一个标签、一组数据、一个消息和一个指针。最主要的区别就是 Tag 对象指向一个commit而不是一个tree。它就像是一个分支引用,但是不会变化,永远指向同一个 commit,仅仅是提供一个更加友好的名字。
5. 查看对象
- 查看“blob”对象:git show + 对象名(SHA1哈希值)
- 查看“tree”对象:git show + 对象名 / git ls-tree + 对象名
- 查看“commit”对象:git show / git log + -s + --pretty=raw +对象名
- 查看“tag”对象:git cat-file tag v1.5.0
6. SHA1哈希值
在Git里随处可见一种“40个字符”的字符串。由于Git中每一个“对象名”都是对“对象”内容做SHA1(SHA1是一种密码学的哈希算法)哈希计算得来的,所以对于内容不同的对象,会有不同的SHA1哈希值,这样就意味着两个不同内容的对象不可能有相同的“对象名”。
例如 6ff87c4664981e4397625791c8ea3bbb5f2279a3
这样做的好处是:
- Git只要比较对象名,就可以很快的判断两个对象是否相同,而且通过检查对象内容的SHA1哈希值和“对象名”是否相同,还可以来判断对象内容是否正确。
- 因为在每个repository的“对象名”的计算方法都完全一样,所以同样的内容存在两个不同的仓库中,都会存在相同的“对象名”下。
7. Git References
在Git中查看完整的提交历史,必须要记得提交的SHA1值,才能找到这些对象,你需要一个文件用一个简单的名字来记录这些 SHA1 值,这样就可以用这些指针而不是原来的 SHA1 值去检索了,我们称之为“引用”(references 或者 refs),可以在 .git/refs 目录下面找到这些包含 SHA1 值的文件,这个目录还不包含任何文件。
$find .git/refs
.git/refs
.git/refs/heads #本地分支的Reference
.git/refs/tags #tag的Reference
.git/refs/remotes #远程追踪分支的Reference
References和分支:
Git 中的一个分支其实就是一个指向某个工作版本一条 HEAD 记录的指针或引用。你可以用这条命令创建一个指向第二次提交的分支。
当执行 git branch (分支名称) 这样的命令,Git 基本上就是执行 update-ref 命令,把你现在所在分支中最后一次提交的 SHA1 值,添加到你要创建的分支的引用。
7.1.Remotes
这就是上面所说的远程引用,如果你添加了一个 remote 然后推送代码过去,Git 会把你最后一次推送到这个 remote 的每个分支的值都记录在refs/remotes 目录下。
Remote 应用和分支主要区别在于他们是不能被 checkout,Git 把他们当作是标记这些了这些分支在服务器上最后状态的一种书签。
8. The Refspec
Reference Specification简称refspec。在执行push或fetch操作时,refspec用以给出本地Ref和远程Ref之间的映射关系,通常是本地分支或本地tag与远程库中的分支或tag之间的映射关系。
refspec格式:
+<src_ref>:<dst_refs>
其中的+是可选的,表示强制更新
典型的push refspec为HEAD:refs/heads/master
典型的fetch refspec为refs/heads/*:refs/remotes/origin/*