Git是当下使用率最高的版本控制管理工具,而且越来越多能的项目都开始采用git作为源码管理工具,掌握如何使用Git基本上快成为一个开发人员的标配技能。要更好的在工作中高效的使用git、处理遇到git问题,就很有必要熟悉了解下Git的内部工作的基本原理。本文通过一个新创建的Git本地仓库来揭示Git的内部存储结构设计。
本文示例的环境基于Windows 10。使用到的工具如下:
- Git Bash v2.23.0 https://github.com/git-for-wi…
- tree v1.8.0 (GBK enhanced):https://github.com/efreykongc…
(下载tree.exe后将其复制到C:\Program Files\Git\usr\bin
目录中)
初识本地仓库的结构
1. Git的几个基本概念和命令回顾
先来回顾下Git的几个概念:
- 工作区:也就是存放项目目录文件的区域,我们开发工作内容都是在工作区域进行。
- 暂存区:则是Git仓库的一个中间区域,介于工作区和版本库之间。要提交到版本库的文件必须先暂存到这个区域。
- 历史版本库:是存放仓库所有历史提交操作的区域。每次提交会产生一个新的版本,并且git会为新版本生成一个40位字符长度SHA-1哈希值的字符串作为新版本的commit id。
开发中最长使用Git命令:git add
:将工作区的指定文件添加到暂存区;git commit
:将暂存区的文件提交到Git历史版本库,产生一个新的版本;git checkout -- <path>
:撤销工作区指定路径(目录文件)的修改,即使用暂存区的副本替换工作区文件;git reset HEAD
:将暂存区的副本替换为指定的历史版本;git reset --hard HEAD
:将全部暂存区的副本、工作区的文件替换为指定的历史版本;git checkout HEAD -- <path>
:将指定路径(目录或文件)的暂存区分布、工作区的文件替换为指定的历史版本。命令效果看起来似乎和上面的git reset --hard HEAD
相同,但是Git内部是有差异的。随后会介绍。
我们用一张图来总结:
<center>图-1</center>
接下来我们用一个实例来观察Git仓库的内部结构。
2. 创建一个本地仓库示例
先在桌面
新建一个名为gitdemo的文件夹,然后右键单击文件夹,选择Git Bash Here
,运行git init
:
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
$ git init
Initialized empty Git repository in C:/Users/efrey/Desktop/gitdemo/.git/
命令执行后会在gitdemo文件夹内新建一个名为.git
的隐藏文件夹。这个文件夹就是git存储数据用的仓库。仔细观察会发现,光标提示符上方的显示信息已经由
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
变成了
efrey@EKStudio MINGW64 ~/Desktop/gitdemo (master)
Git在初始仓库时,会生成名为master
的分支作为仓库的默认分支。新的显示信息表明我们当前的工作是在master分支下进行的。
3. 查看仓库内部结构
gitdemo
文件夹就是工作区(用户在gitdemo内创建的所有子文件夹、文件统称为Working tree),.git
文件夹就是git的仓库。接下来我们在Git bash中运行tree
命令查看git创建的这个仓库里面的目录结构:
efrey@EKStudio MINGW64 ~/Desktop/gitdemo (master)
$ 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
可以看到,.git文件夹中有一个名为HEAD的文件,以及objects的文件夹,但是没有index文件。这是因为现在仓库还是空的,在第一次使用git add
向暂存区暂存文件副本的时候,git会创建index文件。
现在我们就来动手在gitdemo文件夹内新建一个README.md文件,并保存进去一段话:# This is a demo project.
。
再新建一个workspace文件夹,并在这个文件夹内新建一个index.php文件,保存进去下面的内容:
<?php
echo 'Hello world!';
?>
现在用tree
命令查看gitdemo文件夹的结构
efrey@EKStudio MINGW64 ~/Desktop/gitdemo (master)
$ tree
.
├── READMD.md
└── workspace
└── index.php
1 directory, 2 files
上面我们在工作区内新增了一个文件夹和两个文件,接下来要将工作区的内容提交到历史版本库。在Git bash中分别使用git add .
和git commit -m "init"
命令。执行后,我们再次运行tree
命令,查看下git仓库的变化(下面仅列出发生变化的部分):
├── COMMIT_EDITMSG
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 3e
│ │ └── 1c61320591fe8ef00e408db49779e119eaa0d0
│ ├── 48
│ │ └── 9d34e542982cbed668ed1d5cc569b1b4d2eef6
│ ├── 8a
│ │ └── 89279a072641d1ff07e4ed56d208eb216bb68f
│ ├── a1
│ │ └── b92e86716a328cc0369377d4e325d72005f8d0
│ ├── f4
│ │ └── e2a00f95bd95bd5f335761e383b706a6607f3d
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
16 directories, 25 files
可以看到,.git
文件夹中新增了下列几个文件:
index
文件存放着暂存区的文件副本索引–暂存区文件副本文件本身是存放在objects文件夹中的,这个稍后会讲到。
COMMIT_EDITMSG
文件里以文本格式存放着最近一次提交操作的Message。用记事本打开它,里面的内容正是提交时填的init
。
logs\refs\heads\master
从文件名可以直观的看出来这个文件记录了master分支提交操作的日志。我们用记事本打开可以看到格式:
0000000000000000000000000000000000000000 3e1c61320591fe8ef00e408db49779e119eaa0d0 Efrey Kong <efreykong@outlook.com> 1567243601 +0800 commit (initial): init
内容的信息依次为:上一次提交的commit id、本次提交的commit id、提交人、提交时间、提交的说明(message)。
logs\HEAD
HEAD文件是当前分支日志的副本。在我们这个例子里是logsrefsheadsmaster的副本。
refs\heads\master
文件保存的为master分支最近依次提交的HEAD信息,用记事本打开查看:3e1c61320591fe8ef00e408db49779e119eaa0d0
,可以看到这个commit id和我们在log里面看到的本次提交commit id是一致的。
objects\3e\1c61320591fe8ef00e408db49779e119eaa0d0
objects\48\9d34e542982cbed668ed1d5cc569b1b4d2eef6
objects\8a\89279a072641d1ff07e4ed56d208eb216bb68f
objects\a1\b92e86716a328cc0369377d4e325d72005f8d0
objects\f4\e2a00f95bd95bd5f335761e383b706a6607f3d
之前我们提到objects文件夹里面存放的是历史版本库的文件。我们只增加了两个文件,但是objects里面却增加了5个文件。这是为什么呢?
我们在下一篇文章里将详细讲解刚才的提交操作在git仓库里都发生了什么事情。
发表回复