安装完Git,让咱们开始第一次Git之旅。在终端运行下面的命令前端
$git config --global user.name "gaopo"
$git config --global user.email "gaopo@blook.me"
$git init git-demo
$cd git-demo
$echo "Git学习" > README.md
$git add README.md
$git commit -m 'init repo and add README.md'
$git log
复制代码
执行完以上命令,咱们就已经创建了一个Git仓库,而且将文件README.md交由Git管理。 链接远程版本库,把这个README.md分享给别人git
$git remote add origin git@gitlab.blook.me:fe/git-demo.git
$git push origin master
复制代码
团队开发中使用Git的基本流程:bash
实际代码ssh
$git clone git@gitlab.blook.me:fe/git-demo.git
cd git-demo
# 基于远程develop分支创建本地develop分支
$git checkout -b develop origin/develop
# 基于develop分支创建本地特性分支feature
$git branch feature
$git checkout feature
# 在feature分支编写程序 后提交
$git add README.md a.txt
$git commit -m 'add a.txt , change README.md'
# 切换到develop分支,合并feature的修改
$git checkout develop
$git pull
$git merge feature
# 把本地develop分支的修改推到远程develop
$git push
复制代码
在咱们的例子中对应关系以下。gitlab
项目目录:git-demo学习
Git目录:git-demo/.gitfetch
工做目录:git-demo下除了.git目录以外的所有ui
'Git目录'是项目存储全部历史和元信息的目录 - 包括全部的对象(commits,trees,blobs,tags).url
$cd .git ; tree -L 1
|-- HEAD # 记录当前处在哪一个分支里
|-- config # 项目的配置信息,git config命令会改动它
|-- description # 项目的描述信息
|-- hooks/ # 系统默认钩子脚本目录
|-- index # 索引文件
|-- logs/ # 各个refs的历史信息
|-- objects/ # Git本地仓库的全部对象 (commits, trees, blobs, tags)
|-- refs/ # 标识每一个分支指向了哪一个提交(commit)。
复制代码
这个.git目录中还有几个其余的文件和目录,但都不是很重要。不用太关注。spa
HEAD文件就是一个只有一行信息的纯文本文件。这行内容记录的是当前头指针的引用,一般是指向一个分支的引用 ,有时也是一个提交(commit)的SHA值
$ cat .git/HEAD
ref: refs/heads/master #HEAD文件的内容只有这一行,代表当前处于master分支
$ git checkout dd98199
Note: checking out 'dd98199'.
You are in 'detached HEAD' state.
...
$ cat .git/HEAD
dd981999876726a1d31110479807e71bba979c44 #这种状况是”头指针分离“模式,不处于任何分支下。HEAD的值就是某一次commit的SHA
复制代码
config文件记录着项目的配置信息,也是一个普通的纯文本文件。git config命令会改动它(固然也能够手工编辑)。
这个文件里面配置了当前这个版本库的基本属性信息,远程版本库信息,本地分支与远程的映射关系,命令别名等。
总之是一个颇有用的文件。在你的.git目录里看到的config文件内容基本上是下面的样子。
#基本配置
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
#上游版本库
[remote "origin"]
url = http://git.blook.me/fe/git-demo.git
fetch = +refs/heads/*:refs/remotes/origin/*
#本地分支与上游版本库分支的映射
[branch "master"]
remote = origin
merge = refs/heads/master
#当前仓库Git命令别名
[alias]
st = status
复制代码
钩子(hooks)是一些在.git/hooks目录的脚本, 在被特定的事件触发后被调用。当git init命令被 调用后, 一些很是有用的示例钩子脚本被拷到新仓库的hooks目录中; 可是在默认状况下它们是不生效的。 把这些钩 子文件的".sample"文件名后缀去掉就可使它们生效。
pre-commit
。commit
时对说明信息进行格式校验,就是commit-msg
。git暂存区存放index文件中,因此咱们把暂存区有时也叫做索引(index)。索引是一个二进制格式的文件,里面存放了与当前暂存内容相关的信息,包括暂存的文件名、文件内容的SHA1哈希串值和文件访问权限。暂存区是贯穿于整个Git使用流程的重要概念,因此index文件就很重要。因为是二进制因此咱们没法查看具体内容,可是能够用git ls-files --stage 命令查看暂存区里面的文件
$git ls-files --stage
100644 44601d12328ea8e04367337184dcccb85859610e 0 README.md
复制代码
Git对象(blob,tree,commit,tag)都保存在objects目录里面,因此objects目录就是真正的仓库。objects里面的目录结构组织的颇有特色,是以SHA值的前2位做为目录,后38位做为这个目录下的文件名。
refs目录下面是一些纯文本文件,分别记录着本地分支和远程分支的SHA哈希值。文件的数量取决于分支的数量。
$tree refs
refs
├── heads
│ ├── develop # 记录本地develop分支的SHA哈希值
│ └── master # 记录本地master分支的SHA哈希值
├── remotes
│ └── origin
│ ├── develop # 记录远程版本库develop分支的SHA哈希值
│ └── master # 记录远程版本库master分支的SHA哈希值
└── tags
└── v1.0 # 记录里程碑V1.0的SHA哈希值
复制代码
$cat HEAD
ref: refs/heads/master # 说明当前处于master分支
$cat refs/heads/master
dd981999876726a1d31110479807e71bba979c44 # master分支的最新提交SHA哈希值
复制代码
logs目录下面是几个纯文本文件,分别保存着HEAD文件和refs文件内容的历史变化。因为HEAD文件和refs文件的内容就是SHA值,因此log文件的内容就是这些SHA值的变化历史。
commit
加一条记录,发现是个链表结构,首尾相连。 到目前为止 .git目录 里的重要文件目录已经都介绍完了,你们掌握这些就能够了,
全部用来表示项目历史信息的文件,是经过一个40个字符的“对象名”来索引的,对象名看起来像这样:
dd981999876726a1d31110479807e71bba979c44
你会在Git里处处看到这种“40个字符”字符串。每个“对象名”都是对“对象”内容作SHA1哈希计算得来的。
这样就意味着两个不一样内容的对象不可能有相同的“对象名”。
每一个对象(object) 包括三个部分:类型,大小和内容。大小就是指内容的大小,内容取决于对象的类型,Git有四种类型的对象:" blob"、" tree"、 " commit" 和" tag"。
可使用 git show 或 git cat-file -p 命令来查看一个blob对象里的内容。在咱们的例子中 README.md文件对应的 blob对象的SHA1哈希值是 44601d12328ea8e04367337184dcccb85859610e,咱们能够经过下面的的命令来查看blob文件内容:
$ git show 44601d1
Git学习
复制代码
能够经过 git hash-object 命令生成文件的SHA哈希值,若是加上 -w 参数,会把这个文件生成blob对象并写入对象库。 hash-object 命令是个Git比较底层的命令,平时正常使用Git几乎用不到。
$git hash-object README.md
44601d12328ea8e04367337184dcccb85859610e
#这只会显示READMD.md文件blob对象的SHA值,并不会生成blob文件。
#git hash-object -w README.md ,则会真正把README.md生blob对象并写入对象库
复制代码
“tree”有点像一个目录,它管理一些“tree”或是 “blob”(就像文件和子目录) git ls-tree 或 git cat-file -p 命令还能够用来查看tree对象,如今咱们查看刚刚最新提交对应的Tree对象 咱们能够像下面同样来查看它:
$git ls-tree HEAD^{tree}
100644 blob 44601d12328ea8e04367337184dcccb85859610e README.md
040000 tree 16a87dbed191bcfb19a4af9d0cc569f6448a01cc script
复制代码
就如同你所见,一个tree对象包括一串(list)条目,每个条目包括:mode、对象类型、SHA1值 和名字(这串条目是按名字排序的)。它用来表示一个目录树的内容。 一个tree对象能够指向(reference): 一个包含文件内容的blob对象, 也能够是其它包含某个子目录内容的其它tree对象. Tree对象、blob对象和其它全部的对象同样,都用其内容的SHA1哈希值来命名的;只有当两个tree对象的内容彻底相同(包括其所指向全部子对象)时,它的名字才会同样,反之亦然。这样就能让Git仅仅经过比较两个相关的tree对象的名字是否相同,来快速的判断其内容是否不一样。tree对象存储的是指针(tree和blob的SHA哈希值),不存储真正的对象。tree对象能够理解为就是一个目录,目录里包含子目录(tree的SHA值)和文件(blob的SHA值).而SHA值所对应的真正的对象文件存在 .git/objects下面。
一个“commit”只指向一个"tree",它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的做者、指向上次提交(commits)的指针等等。
能够用 git log -1 --pretty=raw 或 git show -s --pretty=raw 或 git cat-file -p <commit>
➜ rrc git:(develop) git cat-file -p HEAD^^
tree f829a37e520fde788a93067d5de87f53e39ffbda
parent 604c39395fbcd4d6ada81207d359974196741085
author gaopo <gaopo@renrenche.com> 1557138527 +0800
committer gaopo <gaopo@renrenche.com> 1557138527 +0800
feat(form): 添加password input
复制代码
父提交 (parent(s)): 提交(commit)的SHA1签名表明着当前提交前一步的项目历史. 上面的那个例子就只有一个父对象; 合并的提交(merge commits)可能会有不仅一个父对象. 若是一个提交没有父对象, 那么咱们就叫它“根提交"(root commit), 它就表明着项目最初的一个版本(revision). 每一个项目必须有至少有一个“根提交"(root commit)。Git就是经过父提交把每一个提交联系起来,也就是咱们通常所说的提交历史。父提交就是当前提交上一版本。
做者 : 作了这次修改的人的名字, 还有修改日期.
提交者(committer): 实际建立提交(commit)的人的名字, 同时也带有提交日期. TA可能会和做者不是同一我的; 例如做者写一个补丁(patch)并把它用邮件发给提交者, 由他来建立提交(commit).
提交说明 :用来描述这次提交.
一个提交(commit)自己并无包括任何信息来讲明其作了哪些修改; 全部的修改(changes)都是经过与父提交(parents)的内容比较而得出的。 通常用 git commit 来建立一个提交(commit), 这个提交(commit)的父对象通常是当前分支(current HEAD), 同时把存储在当前索引(index)的内容所有提交.
commit是使用频率最高的对象,通常在使用Git时,咱们直接接触的就是commit。咱们 commit代码, merge代码, pull / push代码,重置版本库,查看历史,切换分支这些在开发流程中的基本操做都是直接和commit对象打交道。
一个“tag”是来标记某一个提交(commit) 的方法。
一个标签对象包括一个对象名, 对象类型, 标签名, 标签建立人的名字("tagger"), 还有一条可能包含有签名(signature)的消息. 你能够用 git cat-file -p 命令来查看这些信息。
$git tag -m 'create tag from demo' v1.0 #基于当前HEAD创建一个tag,因此tag指向的就是HEAD的引用
$git tag
v1.0
$git cat-file -p v1.0
object e6361ed35aa40f5bae8bd52867885a2055d60ea2
type commit
tag v1.0
tagger gp <gp@demo.com> 1494406971 +0800
create tag from demo
复制代码
Tag对象就是里程碑的做用,通常在咱们正式发布代码是须要创建一个里程碑。
如今咱们已经了解了3种主要对象类型(blob, tree 和 commit), 好如今就让咱们大概了解一下它们怎么组合到一块儿的.
回忆一下如今项目的目录结构:
$tree
.
├── README.md
└── script
├── perl
│ └── test2.pl
└── test1.sh
2 directories, 3 files
复制代码
在Git中它们的存储结构看起来就以下图:
每一个目录都建立了 tree对象, 每一个文件都建立了一个对应的 blob对象 . 最后有一个 commit对象 来指向根tree对象(root of trees), 这样咱们就能够追踪项目每一项提交内容。除了第一个commit,每一个commit对象都有一个父commit对象,父commit就是上一次的提交(历史 history),这样就造成了一条提交历史链。Git就是经过这种方式组成了git版本库
几乎全部的Git功能都是使用这四个简单的对象类型来完成的。它就像是在你本机的文件系统之上构建一个小的文件系统。这个小型的文件系统就是 .git/objects目录。
Git对于咱们的代码管理分了3个区域,分别是工做区,暂存区和版本库。这是Git彻底不一样于SVN的地方。Git之因此强大很大程度上就是由于它设计了3个区域,但同时也是由于这个设计让Git学习起来比较难,上手也比较难,比较难理解。凡事都是有利有弊。
就是电脑上的一个目录,里面是正在开发的工程代码。执行git init 命令后,生成的这个目录除了.git目录,就是工做区。
暂存区最很差理解的一个概念,能够先认为须要提交的文件要先放到暂存区才能提交。因此暂存区能够理解为“提交任务”。是代码提交到版本库前的一个缓冲区域。
.git/index
文件Git的版本库指的是本地仓库,这里面存放着文件的各个版本的数据。其实就是 .git/objects 目录。Git对象都存在这个目录里
历史库,版本库,Git仓库,History,叫法不一样而已,其实指的是同一回事。就是执行了 git commit 后,生成了commit对象。如今咱们知道commit对象包含了提交时间,提交人,提交说明以及提交时的目录树和父提交。那么上面这些都是构成版本库的要素。因为Git管理的全部对象文件都在 .git/objects 目录中,因此版本库概念能够具体形象的理解成 .git/objects 目录里面的对象文件。.git/objects 目录就是版本库,虽然这么说不是十分的准确,可是很是便于记忆和理解。
暂存区,Stage,Cached,Index ,叫法不一样而已,其实指的是同一回事。暂存区就是 .git/index 这个二进制文件,记录着目录和文件的引用(SHA1值),而不是真正的文件对象。真正的文件对象存在于 .git/objects目录里面。
工做区其实就没啥可说的了,就是真实的,看的见摸获得的,正在写的代码。就是你的IDE里面打开的这一堆东西。
再次增强一下记忆: 暂存区就是 .git/index文件 ,版本库就是 .git/objects目录
下面的图例展现了3个区域的关系以及涉及到的主要命令 :git add , git commit , git reset , git checkout
命令 git diff 用来进行具体文件的变更对比,一般用来进行工做区与暂存区之间的对比,实质上是用 git objects 库中的快照与工做区文件的内容的对比。
git add 把当前工做目录中的文件放入暂存区域。
准确的说法是 git add files 作了两件事:
将本地文件的时间戳、长度,当前文档对象的id等信息保存到一个树形目录中去(.git/index,即暂存区)
将本地文件的内容作快照并保存到Git 的对象库(.git/object) 。
从命令的角度来看,git add 能够分两条底层命令实现:
$git hash-object a.txt
$git update-index --add a.txt
#以上两条命令等价于 git add a.txt
复制代码
git commit 命令就是生成一个新的提交,主要干了这么几件事:
git reset HEAD 命令,暂存区的目录树会被重写,被最新提交的目录树所替换,可是工做区不受影响。
“git checkout .” 或者 “git checkout -- <file>”
命令,会用暂存区所有或指定的文件替换工做区的文件。这个操做很危险,会清除工做区中未添加到暂存区的改动。
“git checkout HEAD .” 或者 “git checkout HEAD <file>”
命令,会用最新提交的所有或者部分文件替换暂存区和以及工做区中的文件。这个命令也是极具危险性的,由于不但会清除工做区中未提交的改动,也会清除暂存区中未提交的改动。
恭喜!Git的原理篇就到此。