Git是一个非常强大的版本管理工具,有非常多的概念和命令,也有非常多且复杂的用法。但是其底层模型相对而言却非常简单,了解底层对象模型将非常有助于理解上层命令到底对仓库做了什么。如果想要精通Git,了解底层对象模型也是必不可少的。本文将会通过基本的命令操作,分析每一步操作对仓库数据做了哪些改动,进而分析出Git底层对象模型。
Git基本概念 SHA 所有用来表示项目历史信息的文件,都是通过一个40个字符的(40-digit)“对象名”来索引的,对象名看起来像这样:
6ff87c4664981e4397625791c8ea3bbb5f2279a3
在后边使用对象名时,只使用前面几个字符即可,但是最少需要4个。
你会在Git里到处看到这种“40个字符”字符串。每一个“对象名”都是对“对象”内容做SHA1哈希计算得来的。这样就意味着两个不同内容的对象几乎不可能(理论上是可能发生碰撞的)有相同的“对象名”。
这样做会有几个好处:
只要比较对象名,就可以很快的判断两个对象是否相同。
因为在每个仓库(repository)的“对象名”的计算方法都完全一样,如果同样的内容存在两个不同的仓库中,就会存在相同的“对象名”下。
还可以通过检查对象内容的SHA1的哈希值和“对象名”是否相同,来判断对象内容是否正确。
对象 每个对象(object) 包括三个部分:类型、大小和内容。大小就是指内容的大小,内容取决于对象的类型。
有四种类型的对象:"blob"
、"tree"
、"commit"
和 "tag"
。
几乎所有的Git功能都是使用这四个简单的对象类型来完成的。它就像是在你本机的文件系统之上构建一个小的文件系统。
blob (文件) 对象 blob
用来存储文件数据,通常是一个文件。
tree (目录) 对象 tree
像一个目录,管理 tree(子目录)
或 blob(文件)
commit (提交) 对象 一个 commit
只指向一个 tree
,它用来标记项目某一个特定时间点的状态。commit
保存了树根的 对象名
。
它包括一些关于时间点的元数据,如 时间戳
、最近一次提交的作者
、指向上次提交(commits)的指针
等等。
1 2 3 4 5 6 7 $ git cat-file -p 830 f4857e9 f579818 c 5e69104 d3e2 cc 30 f1 f0 d0 d tree f25061 fa4 f8 b3 bffbf8 ebcc3 ab2351 efdad2 f605 parent 06e13701 ac86 eb09 c 2035329 a5e1 c 18 f95898 cf2 author liuyanjie <x @gmail.com > 1526828841 +0800 committer liuyanjie <x @gmail.com > 1526828841 +0800 commit message
tag (标签) 对象
一个 tag
是来标记某一个 commit
的方法。
实际上 tag
本身是文件名,内容是 commit
的对象名。tag
是 commit
的别名,类似于域名和ip地址的关系。
1 2 $ cat .git/refs/tags/v0.0.0830f4857e9f579818c5e69104d3e2cc30f1f0d0d
commit -> tree -> blob
从图上可以看出:一个 commit
指向了一棵由 tree
和 blob
构成的 Git 对象树。
与其他版本控制系统的区别 Git与你熟悉的大部分版本控制系统的差别是很大的。
也许你熟悉 Subversion
、CVS
、Perforce
、Mercurial
等等,他们使用 “增量文件系统” (Delta Storage systems), 就是说它们存储每次提交(commit)之间的差异。
Git正好与之相反,它会把你的每次提交的文件的全部内容(snapshot)都会记录下来。
这会是在使用Git时的一个很重要的理念。
综上,Git对象模型非常简单,与普通的文件系统非常的相似。
一步一步了解Git在做什么 初始化仓库 初始化目录并创建一个Git仓库
1 2 3 4 5 $ cd git-obj-model $ git init Initialized empty Git repository in /Users/liuyanjie/git-obj-model/.git/
看一下初始化的git仓库中到底有些什么内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ tree .git .git ├── HEAD ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 8 directories, 15 files
1 2 3 4 5 6 7 8 $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true
1 2 $ cat .git/HEAD ref: refs/heads/master
查看仓库状态
1 2 3 4 5 6 7 $ git status On branch master No commits yet nothing to commit (create/copy files and use "git add" to track)
后面忽略 .git/hooks/
下的文件。
增加一个文件README.md 现在创建一个 README.md
,查看 .git
目录,发现没有什么变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ echo "# Readme" > README.md $ tree .git .git ├── HEAD ├── config ├── description ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 8 directories, 15 files
查看当前状态,可以看到,此时有一个未追踪的 README.md
文件,此文件存在于 工作区
中。
1 2 3 4 5 6 7 8 9 10 11 12 $ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) README.md nothing added to commit but untracked files present (use "git add" to track)
执行 git add
命令后,可以看到增加了两个文件:
.git/index
.git/objects/f3/954314c1026028e77ea3a765aadefa67b45195
git add 把文件暂存到索引中为下一次提交做准备,git commit 创建新的提交。
应该知道,这应该是一个 blob
类型的文件,里面存储文件内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ git add README.md $ tree .git .git ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── objects │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads └── tags 9 directories, 17 files
查看当前状态
1 2 3 4 5 6 7 8 9 10 $ git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.md
查看对象内容
1 2 3 $ git cat-file -p f39543
查看暂存区
1 2 3 $ git ls-files --stage 100644 f3954314c1026028e77ea3a765aadefa67b45195 0 README.md
.git/index 是 Git索引文件,是一个在 工作区
和 仓库
间的 暂存区域(staging area)
。
索引是一个二进制格式的文件,里面存放了与当前暂存内容相关的信息,包括暂存的文件名
、文件内容的SHA1哈希串值
和 文件访问权限
,整个索引文件的内容以暂存的文件名进行排git ls-files –stage序保存的。
因为这个文件记录了将要提交的文件, 所以我们才能够多次修改一起提交(commit)。
所以创建了一个新的提交(commit),提交的一般是暂存区里的内容, 而不是工作目录中的内容。
一个Git项目中文件的状态大概分成下面的两大类,而第二大类又分为三小类:
未被跟踪的文件(Untracked files)
已被跟踪的文件(Tracked files)
被修改 未暂存 的文件(Changed but not updated 或 Modified)
被修改 已暂存 可以 被提交 的文件(Changes to be committed 或 Staged)
未修改 的文件(自上次提交以来)(Clean 或 Unmodified)
1 2 3 4 5 6 Changes to be committed: (no files) Changes not staged for commit: (no files) Untracked files: ? .gitignore
执行 git commit
命令,发现路径下又增加了两个文件:
.git/objects/9d/978d59f2f22062c0382c859f4c3ef929026303
.git/objects/c1/067ba0a7ba51f937518c9bc051ea744ca748fe
从上面的结构图,可以想到:
提交可定会产生一个提交对象,提交对象指向一个树对象,树对象包含上一步添加的blob
对象。
下面将通过查看文件内容验证这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 $ git commit -m "First Commmit" README.md [master (root-commit) 033fa1a] First Commmit 1 file changed, 1 insertion(+) create mode 100644 README.md $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 + │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe + │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads │ └── master └── tags 14 directories, 23 files
查看 033f
对象内容,此时不知道这个文件类型及内容是什么
此时,commit
对象为初始提交,所以并无 parent
引用。
1 2 3 4 5 6 7 $ git cat-file -p 033f tree c1067ba0a7ba51f937518c9bc051ea744ca748fe author liuyanjie <x@gmail.com> 1527521894 +0800 committer liuyanjie <x@gmail.com> 1527521894 +0800 First Commmit
查看 c106
对象内容,通过上一步,已知此对象是一个 tree
类型的对象,通过内容可以看到,树类型的对象实际就是存储每行一条数据的列表。
1 2 3 $ git cat-file -p c106 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md
再看 .git/refs/heads/master
文件内容
文件内容只有一行,内容是 033f
对象。master
即是 master
分支的物理表示。
1 2 3 $ cat .git/refs/heads/master 033fa1ab71f0d54f348f07a3a0ffcefd52804df5
同时,还可看到,新增了 .git/logs
目录及内容
该目录下保存了各个分支的提交记录
1 2 3 4 5 6 $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit $ cat .git/logs/HEAD 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit
Tag
1 2 $ git tag v0.0.1 -m 'tag v0.0.1'
查看目录文件变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe │ ├── cc │ │ └── f88a6f1649213499841c33f9bb36d1d8756fb7 │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads │ └── master └── tags └── v0.0.1 15 directories, 25 files
查看 .git/refs/tags/v0.0.1
文件内容,可以看到内容恰好是新增的 objects 对象名
,所以可想而知 ccf8
是一个 tag
类型的对象
1 2 3 $ cat .git/refs/tags/v0.0.1 ccf88a6f1649213499841c33f9bb36d1d8756fb7
查看新增的 ccf8
文件内容,文件指向了 commit
对象 033f
1 2 3 4 5 6 7 8 $ git cat-file -p ccf8 object 033 fa1ab71f0d54f348f07a3a0ffcefd52804df5 type committag v0 .0.1 tagger liuyanjie <x@gmail.com> 1527523284 +0800 tag v0 .0.1
通过以上查看 .git
目录的变化过程,可以大致分析出Git是如何存储这些文件内容的。
上面只执行了 git add
和 git commit
两条操作。
继续添加文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ echo "# CHANGELOG" > CHANGELOG.md $ echo "# CONTRIBUTING" > CONTRIBUTING.md $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) CHANGELOG.md CONTRIBUTING.md nothing added to commit but untracked files present (use "git add" to track) $ git add CHANGELOG.md CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: CHANGELOG.md new file: CONTRIBUTING.md
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 │ ├── a0 │ │ └── cf709bc0991b5340080f944d02894dc1596d46 │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe │ ├── c6 │ │ └── b9e95b39b8cd8ead8bbf4b118104741017de1b │ ├── cc │ │ └── f88a6f1649213499841c33f9bb36d1d8756fb7 │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads │ └── master └── tags └── v0.0.1 17 directories, 27 files
1 2 3 4 5 $ git ls-files --stage 100644 a0cf709bc0991b5340080f944d02894dc1596d46 0 CHANGELOG.md 100644 c6b9e95b39b8cd8ead8bbf4b118104741017de1b 0 CONTRIBUTING.md 100644 f3954314c1026028e77ea3a765aadefa67b45195 0 README.md
分别查看一下各个文件的内容
1 2 3 4 5 6 7 8 9 $ git cat-file -p a0cf $ git cat-file -p c6b9 $ git cat-file -p f395
再查看一下树对象的内容,然而并没有任何变化
1 2 3 $ git cat-file -p c106 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md
下面提交这两个文件,可以通过日志方便的查看信息。
1 2 3 4 5 6 $ git commit --all -m "add CHANGELOG.md and CONTRIBUTING.md files" [master 28baf4f] add CHANGELOG.md and CONTRIBUTING.md files 2 files changed, 2 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md
同样再看一下.git
目录下的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 │ ├── 28 │ │ └── baf4f77fb49abf99c18bc1c12363d898f3ced7 │ ├── 2c │ │ └── affd90cd736e58f516e3988e3af84f5fa42b4f │ ├── a0 │ │ └── cf709bc0991b5340080f944d02894dc1596d46 │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe │ ├── c6 │ │ └── b9e95b39b8cd8ead8bbf4b118104741017de1b │ ├── cc │ │ └── f88a6f1649213499841c33f9bb36d1d8756fb7 │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads │ └── master └── tags └── v0.0.1 19 directories, 29 files
查看一下master上的提交日志,刚刚提交内容在新的一行,与首次提交稍微有点差别,首次提交是commit (initial)。
还可以发现第二次提交的第一列和第一次提交的第二列一样,可以猜到,第一列指向上一次的提交对象。
1 2 3 4 $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 28baf4f77fb49abf99c18bc1c12363d898f3ced7 liuyanjie <x@gmail.com> 1527524452 +0800 commit: add CHANGELOG.md and CONTRIBUTING.md files
查看 28ba
1 2 3 4 5 6 7 8 $ git cat-file -p 28ba tree 2caffd90cd736e58f516e3988e3af84f5fa42b4f parent 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 author liuyanjie <x@gmail.com> 1527524452 +0800 committer liuyanjie <x@gmail.com> 1527524452 +0800 add CHANGELOG.md and CONTRIBUTING.md files
相比 033f
,28ba
多了 parent 字段 且 parent 字段值是 033f
同时 master
也指向了新的提交对象。
第一次产生的提交对象同样存在于目录当中。
1 2 3 $ cat .git/refs/heads/master 28baf4f77fb49abf99c18bc1c12363d898f3ced7
在看树对象2caf
,发现相比之前,多了两行,分别指向新增加的文件。
README.md
文件出现在了 2caf
对象中,实际上,它同时还存在于 c106
对象中。
1 2 3 4 $ git cat-file -p 2caf 100644 blob a0cf709bc0991b5340080f944d02894dc1596d46 CHANGELOG.md 100644 blob c6b9e95b39b8cd8ead8bbf4b118104741017de1b CONTRIBUTING.md 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md
1 2 3 $ git cat-file -p c106 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md
到现在为止,整个目录下有3个文件,而 .git
目录下已经有多个文件,为了跟踪记录版本。
1 2 3 $ ls CHANGELOG.md CONTRIBUTING.md README.md
通过以上 .git
目录变化,可以发现:
每次提交都会产出一 commit
对象,这些 commit
通过 parent
,形成一个由高版本到低版本的链表,追溯这个链表,可以回溯到任意版本。
commit
对象保存一个 tree
的根节点,根节点下面再包含 blob
或 tree
,类似普通文件系统结构,和上面的图一致,从根节点开始,可以找到某一般版本下的所有文件。
在每一颗树下,因为都是使用类似指针的结构,所以每次修改都是将变化的文件,重新创建一个blob文件,并修改相应指针。
目录中的 .git/refs/heads/master
指向某一次提交,当由另外一个分支的时候,会有 .git/refs/heads/branch-xxx
文件指向另外一次提交,而初始时与父分支指向相同。
再添加一个带有目录文件 1 2 3 4 5 6 7 8 9 10 11 $ mkdir lib $ echo "// Author: liuyanjie" > ./lib/index.js $ git add lib/index.js $ git commit -m "add lib/index.js" lib/index.js [master 2b9fc85] add lib/index.js 1 file changed, 1 insertion(+) create mode 100644 lib/index.js
1 2 3 4 5 6 7 8 $ git cat-file -p 2b9fc85 tree 9609811e44367d44f2915435f4454716e1e535fd parent 28baf4f77fb49abf99c18bc1c12363d898f3ced7 author liuyanjie <x@gmail.com> 1527556745 +0800 committer liuyanjie <x@gmail.com> 1527556745 +0800 add lib/index.js
1 2 3 4 5 $ git cat-file -p 9609 100644 blob a0cf709bc0991b5340080f944d02894dc1596d46 CHANGELOG.md 100644 blob c6b9e95b39b8cd8ead8bbf4b118104741017de1b CONTRIBUTING.md 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md 040000 tree 2fb9045bb558889ea2bd8cc5d8fe45e7247706da lib
1 2 3 $ git cat-file -p 2fb9 100644 blob 39a204af28de9b4f0411735e597e0da7416ca35a index.js
上面连续几步和之前的效果一样,但是可以看到,在树对象 9609
中,包含了另一个树对象 2fb9
,这个对象的内容指向index.js
文件。
1 2 3 4 5 $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 28baf4f77fb49abf99c18bc1c12363d898f3ced7 liuyanjie <x@gmail.com> 1527524452 +0800 commit: add CHANGELOG.md and CONTRIBUTING.md files 28baf4f77fb49abf99c18bc1c12363d898f3ced7 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527556745 +0800 commit: add lib/index.js
git log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 commit 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 (HEAD -> master) Author: liuyanjie <x@gmail.com> Date: Tue May 29 09:19:05 2018 +0800 add lib/index.js commit 28baf4f77fb49abf99c18bc1c12363d898f3ced7 Author: liuyanjie <x@gmail.com> Date: Tue May 29 00:20:52 2018 +0800 add CHANGELOG.md and CONTRIBUTING.md files commit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 (tag: v0.0.1) Author: liuyanjie <x@gmail.com> Date: Mon May 28 23:38:14 2018 +0800 First Commmit
开始创建分支 创建并切换到分支 feature-a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ git branch -v * master 2b9fc85 add lib/index.js $ git checkout -b feature-a Switched to a new branch 'feature-a' $ git branch -v * feature-a 2b9fc85 add lib/index.js master 2b9fc85 add lib/index.js $ git status On branch feature-a nothing to commit, working tree clean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ ├── feature-a │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 │ ├── 28 │ │ └── baf4f77fb49abf99c18bc1c12363d898f3ced7 │ ├── 2b │ │ └── 9fc8524bac21d5d5c2f988b5793315ce93abc6 │ ├── 2c │ │ └── affd90cd736e58f516e3988e3af84f5fa42b4f │ ├── 2f │ │ └── b9045bb558889ea2bd8cc5d8fe45e7247706da │ ├── 39 │ │ └── a204af28de9b4f0411735e597e0da7416ca35a │ ├── 96 │ │ └── 09811e44367d44f2915435f4454716e1e535fd │ ├── a0 │ │ └── cf709bc0991b5340080f944d02894dc1596d46 │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe │ ├── c6 │ │ └── b9e95b39b8cd8ead8bbf4b118104741017de1b │ ├── cc │ │ └── f88a6f1649213499841c33f9bb36d1d8756fb7 │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── info │ └── pack └── refs ├── heads │ ├── feature-a │ └── master └── tags └── v0.0.1 23 directories, 35 files
看一下.git
文件内容,可以看到.git/refs/heads
目录下多了个feature-a
查看一下 feature-a
的相关内容,指向的提交对象和 master
一样,并指明 Created from HEAD
。
从内容中可以看出,两个分支指向同一个提交对象 1ad0e5
,但是两个分支的日志不同。
日志记录了分支的历史,而提交对象记录了分支的数据内容和所有分支的历史。
1 2 3 4 5 $ cat .git/refs/heads/feature-a 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 $ cat .git/refs/heads/master 2b9fc8524bac21d5d5c2f988b5793315ce93abc6
1 2 3 4 5 6 7 $ cat .git/logs/refs/heads/feature-a 0000000000000000000000000000000000000000 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527557101 +0800 branch: Created from HEAD $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 28baf4f77fb49abf99c18bc1c12363d898f3ced7 liuyanjie <x@gmail.com> 1527524452 +0800 commit: add CHANGELOG.md and CONTRIBUTING.md files 28baf4f77fb49abf99c18bc1c12363d898f3ced7 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527556745 +0800 commit: add lib/index.js
初始化package.json
1 2 $ npm init Is this ok? (yes )
安装 bluebird
1 2 3 4 5 6 7 8 $ npm install bluebird --save npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN git-obj-model@1.0.0 No description npm WARN git-obj-model@1.0.0 No repository field. + bluebird@3.5.1 added 1 package in 1.999s
添加文件到版本库,并查看变化。
1 2 3 4 5 6 7 8 $ git add package.json $ cat .git/refs/heads/feature-a 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 $ cat .git/refs/heads/master 2b9fc8524bac21d5d5c2f988b5793315ce93abc6
提交文件到版本库,并查看变化,提交指针已经指向新的提交对象,分支的版本超前于master,因为parent指向1ad0e5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $ git commit package.json -m 'add package.json' [feature-a 74ce192] add package.json 1 file changed, 17 insertions(+) create mode 100644 package.json $ cat .git/refs/heads/master 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 $ cat .git/refs/heads/feature-a 74ce19245be785772b33e1193df0d2c6a940ed10 $ git cat-file -p 74ce tree 9d23f8ad2ecc9f8e49d78174bcc6e4668ca7f031 parent 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 author liuyanjie <x@gmail.com> 1527557656 +0800 committer liuyanjie <x@gmail.com> 1527557656 +0800 add package.json $ git cat-file -p 9d23 100644 blob a0cf709bc0991b5340080f944d02894dc1596d46 CHANGELOG.md 100644 blob c6b9e95b39b8cd8ead8bbf4b118104741017de1b CONTRIBUTING.md 100644 blob f3954314c1026028e77ea3a765aadefa67b45195 README.md 040000 tree 2fb9045bb558889ea2bd8cc5d8fe45e7247706da lib 100644 blob 5ee275ce022007c38d187c58aef13f8d9e04b553 package.json
合并分支 feature-a
到 master
分支,可以看到 master
分支跟上了 feature-a
,只改指针既可,非常快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ git checkout master Switched to branch 'master' $ git merge feature-a --no-ff Merge made by the 'recursive' strategy. package.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 package.json $ cat .git/refs/heads/master b98c36254dba6e169429c2be57b1bbeccb6e1f28 $ cat .git/refs/heads/feature-a 74ce19245be785772b33e1193df0d2c6a940ed10
1 2 3 4 5 6 7 8 $ tig 2018-05-29 09:37 liuyanjie M─┐ [master] Merge branch 'feature-a' 2018-05-29 09:34 liuyanjie │ o [feature-a] add package.json 2018-05-29 09:19 liuyanjie o─┘ add lib/index.js 2018-05-29 00:20 liuyanjie o add CHANGELOG.md and CONTRIBUTING.md files 2018-05-28 23:38 liuyanjie I <v0.0.1> First Commmit
修改 master
分支,编辑文件并提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ git checkout master Already on 'master' $ echo -e "\n\n# About" >> README.md $ git commit README.md -m "add About Me" [master cc52745] add About Me 1 file changed, 4 insertions(+) $ cat README.md
修改 feature-a
分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ git checkout feature-a Switched to branch 'feature-a' $ cat README.md $ echo -e "\n\n# About" >> README.md $ cat README.md $ git add README.md $ git commit README.md -m "add About Me" [feature-a fd885cf] add About Me 1 file changed, 3 insertions(+)
查看分支头和历史记录,可以看到分支头指向不同的提交对象,而提交对象,又来源于同一个提交对象0cb214741c044af3fb4677fe72e3ae175f3e0358
,两个分支之间存在交叉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ cat .git/refs/heads/feature-a 74ce19245be785772b33e1193df0d2c6a940ed10 $ cat .git/refs/heads/master cc52745b13b00c672c7ac9b1dc42336953293b7a $ cat .git/logs/refs/heads/feature-a 0000000000000000000000000000000000000000 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527557101 +0800 branch: Created from HEAD 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 74ce19245be785772b33e1193df0d2c6a940ed10 liuyanjie <x@gmail.com> 1527557656 +0800 commit: add package.json 74ce19245be785772b33e1193df0d2c6a940ed10 fd885cfcd548a24fabb2f5b49ea60beadc8c5912 liuyanjie <x@gmail.com> 1527558668 +0800 commit: add About Me $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 28baf4f77fb49abf99c18bc1c12363d898f3ced7 liuyanjie <x@gmail.com> 1527524452 +0800 commit: add CHANGELOG.md and CONTRIBUTING.md files 28baf4f77fb49abf99c18bc1c12363d898f3ced7 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527556745 +0800 commit: add lib/index.js 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 b98c36254dba6e169429c2be57b1bbeccb6e1f28 liuyanjie <x@gmail.com> 1527557853 +0800 merge feature-a: Merge made by the 'recursive' strategy. b98c36254dba6e169429c2be57b1bbeccb6e1f28 cc52745b13b00c672c7ac9b1dc42336953293b7a liuyanjie <x@gmail.com> 1527558514 +0800 commit: add About Me
切换到 master
分支,再次合并 feature-a
到 master
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ git checkout master Switched to branch 'master' $ git merge feature-a Auto-merging README.md CONFLICT (content): Merge conflict in README.md Automatic merge failed; fix conflicts and then commit the result. $ cat README.md <<<<<<< HEAD ## About ======= ## About >>>>>>> feature-a
解决冲突之后的文件
1 2 3 4 5 6 7 8 9 10 11 $ git status On branch master You have unmerged paths. (fix conflicts and run "git commit" ) (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: README.md
1 2 3 4 5 $ git commit README.md -m 'fix conflict' fatal: cannot do a partial commit during a merge. $ git commit -a [master 5a68c74] Merge branch 'feature-a'
1 2 3 4 5 $ cat .git/refs/heads/feature-a fd885cfcd548a24fabb2f5b49ea60beadc8c5912 $ cat .git/refs/heads/master 5a68c7402c30ae44fd82dadba2d8f148efb75541
1 2 3 4 5 6 7 8 $ cat .git/logs/refs/heads/master 0000000000000000000000000000000000000000 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 liuyanjie <x@gmail.com> 1527521894 +0800 commit (initial): First Commmit 033fa1ab71f0d54f348f07a3a0ffcefd52804df5 28baf4f77fb49abf99c18bc1c12363d898f3ced7 liuyanjie <x@gmail.com> 1527524452 +0800 commit: add CHANGELOG.md and CONTRIBUTING.md files 28baf4f77fb49abf99c18bc1c12363d898f3ced7 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 liuyanjie <x@gmail.com> 1527556745 +0800 commit: add lib/index.js 2b9fc8524bac21d5d5c2f988b5793315ce93abc6 b98c36254dba6e169429c2be57b1bbeccb6e1f28 liuyanjie <x@gmail.com> 1527557853 +0800 merge feature-a: Merge made by the 'recursive' strategy. b98c36254dba6e169429c2be57b1bbeccb6e1f28 cc52745b13b00c672c7ac9b1dc42336953293b7a liuyanjie <x@gmail.com> 1527558514 +0800 commit: add About Me cc52745b13b00c672c7ac9b1dc42336953293b7a 5a68c7402c30ae44fd82dadba2d8f148efb75541 liuyanjie <x@gmail.com> 1527559277 +0800 commit (merge): Merge branch 'feature-a'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ git merge --stat feature-a Already up to date . $ git branch -d feature-a Deleted branch feature-a (was fd885cf). $ git branch $ git cat-file -p fd88 tree 82e47e13f87031cab7c47b455744bc4ed3fdba3c parent 74ce19245be785772b33e1193df0d2c6a940ed10 author liuyanjie <x@gmail.com> 1527558668 +0800 committer liuyanjie <x@gmail.com> 1527558668 +0800 add About Me $ git cat-file -p 5a68 tree 42564bab966d59e3501a1903c1cb2066adc2bd2d parent cc52745b13b00c672c7ac9b1dc42336953293b7a parent fd885cfcd548a24fabb2f5b49ea60beadc8c5912 author liuyanjie <x@gmail.com> 1527559277 +0800 committer liuyanjie <x@gmail.com> 1527559277 +0800 Merge branch 'feature-a'
1 2 3 4 5 6 7 8 9 10 11 12 13 $ git log --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date =relative * 5a68c74 - (HEAD -> master) Merge branch 'feature-a' (7 minutes ago) <liuyanjie> |\ | * fd885cf - add About Me (17 minutes ago) <liuyanjie> * | cc52745 - add About Me (20 minutes ago) <liuyanjie> * | b98c362 - Merge branch 'feature-a' (31 minutes ago) <liuyanjie> |\ \ | |/ | * 74ce192 - add package.json (34 minutes ago) <liuyanjie> |/ * 2b9fc85 - add lib/index.js (49 minutes ago) <liuyanjie> * 28baf4f - add CHANGELOG.md and CONTRIBUTING.md files (10 hours ago) <liuyanjie> * 033fa1a - (tag: v0.0.1) First Commmit (11 hours ago) <liuyanjie>
配置命令别名
1 2 $ git config --global alias.lg "log --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative" git lg
打标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ git tag -a v0.0.2 -m "v0.0.2" v0.0.1 v0.0.2 $ cat .git/refs/tags/v0.0.2 174c530154ab3b4d4d322bcbd66cdde18882eb00 $ git cat-file -p 174c object 5a68c7402c30ae44fd82dadba2d8f148efb75541 type committag v0.0.2 tagger liuyanjie <x@gmail.com> 1527559851 +0800 v0.0.2 $ git cat-file -p 5a68 tree 42564bab966d59e3501a1903c1cb2066adc2bd2d parent cc52745b13b00c672c7ac9b1dc42336953293b7a parent fd885cfcd548a24fabb2f5b49ea60beadc8c5912 author liuyanjie <x@gmail.com> 1527559277 +0800 committer liuyanjie <x@gmail.com> 1527559277 +0800 Merge branch 'feature-a'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 $ tree .git .git ├── COMMIT_EDITMSG ├── HEAD ├── ORIG_HEAD ├── co.gitup.mac │ ├── cache.db │ ├── info.plist │ └── snapshots.data ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── index ├── info │ └── exclude ├── logs │ ├── HEAD │ └── refs │ └── heads │ └── master ├── objects │ ├── 03 │ │ └── 3fa1ab71f0d54f348f07a3a0ffcefd52804df5 │ ├── 17 │ │ └── 4c530154ab3b4d4d322bcbd66cdde18882eb00 │ ├── 28 │ │ └── baf4f77fb49abf99c18bc1c12363d898f3ced7 │ ├── 2b │ │ └── 9fc8524bac21d5d5c2f988b5793315ce93abc6 │ ├── 2c │ │ └── affd90cd736e58f516e3988e3af84f5fa42b4f │ ├── 2f │ │ └── b9045bb558889ea2bd8cc5d8fe45e7247706da │ ├── 30 │ │ └── be8054b61a4a124e4cd8a1201de8474cb45e12 │ ├── 39 │ │ └── a204af28de9b4f0411735e597e0da7416ca35a │ ├── 42 │ │ └── 564bab966d59e3501a1903c1cb2066adc2bd2d │ ├── 44 │ │ └── 2b821b9b4acb5b0e632042542b3b29e2cf721e │ ├── 48 │ │ └── 1cab7467cdb697bf7affdba0f2a5673a03366d │ ├── 5a │ │ └── 68c7402c30ae44fd82dadba2d8f148efb75541 │ ├── 5e │ │ └── e275ce022007c38d187c58aef13f8d9e04b553 │ ├── 74 │ │ └── ce19245be785772b33e1193df0d2c6a940ed10 │ ├── 82 │ │ └── e47e13f87031cab7c47b455744bc4ed3fdba3c │ ├── 96 │ │ └── 09811e44367d44f2915435f4454716e1e535fd │ ├── 9d │ │ └── 23f8ad2ecc9f8e49d78174bcc6e4668ca7f031 │ ├── a0 │ │ └── cf709bc0991b5340080f944d02894dc1596d46 │ ├── b9 │ │ └── 8c36254dba6e169429c2be57b1bbeccb6e1f28 │ ├── c1 │ │ └── 067ba0a7ba51f937518c9bc051ea744ca748fe │ ├── c6 │ │ └── b9e95b39b8cd8ead8bbf4b118104741017de1b │ ├── cc │ │ ├── 52745b13b00c672c7ac9b1dc42336953293b7a │ │ └── f88a6f1649213499841c33f9bb36d1d8756fb7 │ ├── f3 │ │ └── 954314c1026028e77ea3a765aadefa67b45195 │ ├── fd │ │ └── 885cfcd548a24fabb2f5b49ea60beadc8c5912 │ ├── info │ └── pack └── refs ├── heads │ └── master └── tags ├── v0.0.1 └── v0.0.2 36 directories, 51 files
https://stackoverflow.com/questions/964876/head-and-orig-head-in-git
.git/HEAD 指明了当前活跃分支是哪个分支
1 2 3 $ cat .git/HEAD ref: refs/heads/master
svg版
查看源文件 编辑源文件