深入理解git分支(一)


本文将从0开始构建一个git项目,介绍.git目录下各个文件夹/文件的含义,并且通过添加/编辑文件的过程介绍git对象的原理。本文只涉及单分支master,对于多分支的情况将在下一篇文章 深入理解git分支(二)中介绍。

初始化项目

新建项目文件夹并在文件夹路径下运行命令初始化项目:

git init

项目初始化后的文件结构如下:

|—— .git
	|—— hooks 一些钩子脚本
	|—— info 存放仓库的一些信息
	|—— objects git数据库,存放所有git对象
		|—— info
		|—— pack
	|—— refs 保存指向各分支commit对象的指针
		|—— heads
		|—— tags
	|—— config 文件,配置文件
	|—— description 文件,描述信息
	|—— HEAD 文件,指向当前分支

添加文件并提交

在项目中添加一些文件和文件夹:

|—— .git
|—— dir1
	|—— file2.txt
|—— file1.txt

运行命令:

git add .
git commit -m "add files and dir"

此时查看.git文件夹:

|—— .git
	|—— hooks
	|—— info
+	|—— logs
		|—— refs
			|—— heads
				|—— master
		|—— HEAD
	|—— objects
+		|—— 2d
			|—— e2ca1110497724be5af3322611d2c4b2c79a9d
+		|—— 6e
			|—— 44180b8f0cb06279626d54a8d0b7a77feefbd9
+		|—— 21
			|—— 895d3c9a9d047fce8cc40c86a8274f2096541f
+		|—— b0
			|—— 62f1906d5c772df480b171bd52e5c15415c1e1
+		|—— e6
			|—— 9daa91f260c60a293ed816277b26b8d5420849
		|—— info
		|—— pack
	|—— refs
		|—— heads
+			|—— master
		|—— tags
+	|—— COMMIT_EDITMSG
	|—— config
	|—— description
	|—— HEAD
+	|—— index

变化:

  1. logs文件夹,保存日志
  2. objects文件夹,由于进行了git commit操作,git数据库发生了变化,objects文件夹中产生的每个文件都对应一个git对象
  3. refs文件夹中新增的master文件保存了最近一个commit对象的索引,本例中为"e69daa91f260c60a293ed816277b26b8d5420849"(objects中最后一个git对象)
  4. HEAD文件的内容被更新为"ref: refs/heads/master",因此也是"e69daa91f260c60a293ed816277b26b8d5420849"
  5. COMMIT_EDITMSG文件记录了git commit时输入的信息,在本例中即"add files and dir"
  6. index文件保存暂存区的信息,关于git文件状态和分区,请参考git文件状态

关于git对象

上述操作结束后objects文件夹/git数据库中新增了5个对象,文件夹名+文件名(共40个字符)即为对象的哈希值(git使用的哈希算法是SHA-1)。这5个对象可以分为3类:

  1. blob对象,对应于每个文件,对于一个文件,每次变更都会产生一个新的blob对象。本例中有两个文件变更即file1.txt和file2.txt,因此有2个blob对象
  2. tree对象,对应每个文件夹(根文件夹也算),对于一个文件夹,每次变更都会产生一个新的tree对象,该对象包含一些其内部文件夹/文件对应的tree对象和blob对象的指针。本例中有两个文件夹变更即根目录和dir1,因此有2个tree对象
  3. commit对象,对应每次commit,每次commit都会产生一个新的commit对象,该对象包含作者信息(用户名和邮箱,可通过git config user.name和git config user.email查看)、提交时输入的信息(-m后的信息)、指向当前路径对应的tree对象的指针和父commit对象指针(可为空)。本例commit了一次,因此有1个commit对象

那么问题来了,如何知道objects下的对象的具体信息呢?

查看git对象

通过哈希值查看

在已知对象哈希值的情况下,使用git cat-file命令查看对象信息,上述5个对象分别如下:

// commit对象,包含tree指针,author, committer和输入的信息,父commit对象为空,因为这是第一个commit
git cat-file -p e69daa91f260c60a293ed816277b26b8d5420849

tree 2de2ca1110497724be5af3322611d2c4b2c79a9d
author test <test@test.com> 1580282713 +0800
committer test <test@test.com> 1580282713 +0800

add files and dir
// 根目录对应的tree对象,包含指向dir1 tree对象和file1.txt blob对象的指针
git cat-file -p 2de2ca1110497724be5af3322611d2c4b2c79a9d

040000 tree 6e44180b8f0cb06279626d54a8d0b7a77feefbd9    dir1
100644 blob b062f1906d5c772df480b171bd52e5c15415c1e1    file1.txt
// 目录dir1对应的tree对象,包含指向file2.txt blob对象的指针
git cat-file -p 6e44180b8f0cb06279626d54a8d0b7a77feefbd9

100644 blob 21895d3c9a9d047fce8cc40c86a8274f2096541f    file2.txt
// file1.txt对应的blob对象,包含file1.txt的内容
git cat-file -p b062f1906d5c772df480b171bd52e5c15415c1e1

This is file1.
// file2.txt对应的blob对象,包含file2.txt的内容
git cat-file -p 21895d3c9a9d047fce8cc40c86a8274f2096541f 

This is file2.
通过master^{type}语法查看

还可以通过master^{type}语法查看git对象:

// 查看最近的commit对象
git cat-file -p master^{commit}

tree 2de2ca1110497724be5af3322611d2c4b2c79a9d
author test <test@test.com> 1580282713 +0800
committer test <test@test.com> 1580282713 +0800

add files and dir
// 查看最近的tree对象
git cat-file -p master^{tree}

040000 tree 6e44180b8f0cb06279626d54a8d0b7a77feefbd9    dir1
100644 blob b062f1906d5c772df480b171bd52e5c15415c1e1    file1.txt

git对象的关系

简单来说,3类git对象的关系是:commit对象指向tree对象,tree对象指向其他tree对象或blob对象,blob对象代表文件。本例中的5个git对象关系是:
git对象关系

HEAD, index和文件状态

.git文件状态中提到,git项目中的文件有3种状态:modified, staged和committed,那么git如何知道每个文件处于何种状态呢?HEAD文件和index文件就是用来做这件事的。

HEAD文件

本例中HEAD文件的内容是ref: refs/heads/master,引用了refs下的一个文件,打开refs/heads/master文件,其内容是e69daa91f260c60a293ed816277b26b8d5420849,这是最近的一个commit对象,而通过这个commit对象git可以拿到所有文件的最新版本(上一节也介绍了如何通过git cat-file来查询),所以可以认为HEAD文件实际上保存了所有处于committed状态的文件信息。

index文件

.git/index文件是一个二进制文件,它保存了所有文件最近一次暂存(git add)的信息,可以使用命令git ls-files --stage查看index文件的内容:

git ls-files --stage
100644 21895d3c9a9d047fce8cc40c86a8274f2096541f 0       dir1/file2.txt
100644 b062f1906d5c772df480b171bd52e5c15415c1e1 0       file1.txt

因此,通过对比index文件和git项目中的文件的哈希值,就可以知道哪些文件处于modified状态,即还未git add;通过对比index文件和HEAD文件就可以知道哪些文件处于staged状态,哪些文件处于committed状态,即git add后还未git commit。此外,如果index文件不存在(初始化项目时),则说明所有文件处于untracked状态,也还未git add。

如果用一段伪代码来表示判断文件状态的逻辑:

遍历git项目中的所有文件:
	if index文件不存在:
		return "untracked"
	else:
		var oldHash = 从index文件中读取
		var newHash = SHA1(文件)
		if oldHash != newHash:
			return "modified"
		else:
			var committedHash = 通过HEAD文件找到
			if newHash != committedHash:
				return "staged"
			else:
				return "committed"

编辑file1.txt文件

下面通过编辑file1.txt文件看一看文件状态和git对象的变化。

  1. 编辑file1.txt文件,然后运行git status,file1.txt文件的状态是modified

    git status
    On branch master
    Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git checkout -- <file>..." to discard changes in working directory)
    
        modified:   file1.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
  2. 运行git add命令,再用git ls-files --stage查看index文件内容,发现file1.txt的哈希值更新了,如果再运行git status,发现file1.txt的状态变为staged

    git ls-files --stage
    100644 21895d3c9a9d047fce8cc40c86a8274f2096541f 0       dir1/file2.txt
    - 100644 b062f1906d5c772df480b171bd52e5c15415c1e1 0       file1.txt
    + 100644 8210a3da97594bfc4c427dd481907895f98a9144 0       file1.txt
    
  3. 运行git commit,然后查看refs/heads/master,文件内容已经更新为新的commit对象

  4. 查看objects文件夹,发现多了3个对象(下图加框),这3个对象是一个blob对象、一个tree对象和一个commit对象,commit对象的parent指向上一个commit对象
    在这里插入图片描述

小结

git项目的.git文件夹保存了所有关于对象、工作区、文件状态的数据,其中objects文件夹保存了所有git对象(commit对象、tree对象和blob对象);refs文件夹保存了所有分支最新commit的指针;HEAD文件通过引用refs下的文件掌握了所有.git仓库目录下的文件(即committed状态的文件);index文件保存了所有暂存区的文件(即staged状态的文件),通过HEAD和index文件就可以知道每个文件的状态。
本文的介绍基于单分支master,下一篇文章深入理解git分支(二)将继续介绍多分支的情况。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值