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 --
:撤销工作区指定路径(目录文件)的修改,即使用暂存区的副本替换工作区文件;git reset HEAD
:将暂存区的副本替换为指定的历史版本;git reset --hard HEAD
:将全部暂存区的副本、工作区的文件替换为指定的历史版本;git checkout HEAD --
:将指定路径(目录或文件)的暂存区分布、工作区的文件替换为指定的历史版本。命令效果看起来似乎和上面的git reset --hard HEAD
相同,但是Git内部是有差异的。随后会介绍。
我们用一张图来总结:
接下来我们用一个实例来观察Git仓库的内部结构。
2. 创建一个本地仓库示例
先在桌面
右键单击,在弹出的菜单上选择Git Bash Here
,在打开的Git Bash命令行中依次运行:
efrey@EKStudio MINGW64 ~/Desktop
$ mkdir gitdemo
efrey@EKStudio MINGW64 ~/Desktop
$ cd gitdemo
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
$ git init
Initialized empty Git repository in C:/Users/efrey/Desktop/gitdemo/.git/
命令执行后会在桌面上新建一个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文件夹内新建两个文件。在Git bash中运行:
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
$ echo "# This is a demo project." > README.md
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
$ mkdir src
efrey@EKStudio MINGW64 ~/Desktop/gitdemo
$ echo -e '' > src/index.php
README.md和src/index.php文件已经生成好了,接下来要将工作区的内容提交到历史版本库。在Git bash中分别使用git add .
和git commit -m "init"
命令。执行后,我们再次运行tree
命令,查看下git仓库的变化(下面仅列出发生变化的部分):
├── COMMIT_EDITMSG
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 23
│ │ └── c6e2c6e5bd959951813965d6cc607dbbd28fee
│ ├── 32
│ │ └── 454a9530d7b66fd8b35d2d5b5ea58adbf98e82
│ ├── 46
│ │ └── a2eb9234dc84c7b0c369afe067f6f1c3794500
│ ├── 54
│ │ └── 2123498f1065f5e063576f54acda8d535f5538
│ ├── 9c
│ │ └── 2ba432d595da5b3e004fb0a10232be67dc7937
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
16 directories, 23 files
可以看到,.git
文件夹中新增了下列几个文件:
index
文件存放着暂存区的文件副本索引--暂存区文件副本文件本身是存放在objects文件夹中。
COMMIT_EDITMSG
文件里以文本格式存放着最近一次提交操作的Message。用记事本打开它,里面的内容正是提交时填的init
。
logs\refs\heads\master
从文件名可以直观的看出来这个文件记录了master分支提交操作的日志。我们用记事本打开可以看到格式:
0000000000000000000000000000000000000000 32454a9530d7b66fd8b35d2d5b5ea58adbf98e82 Efrey Kong 1567262832 +0800 commit (initial): init
内容的信息依次为:上一次提交的commit id、本次提交的commit id、提交人、提交时间、提交的说明(message)。
logs\HEAD
HEAD文件是当前分支日志的副本。在我们这个例子里是logsrefsheadsmaster的副本。
refs\heads\master
文件保存的为master分支最近依次提交的HEAD信息,用记事本打开查看:32454a9530d7b66fd8b35d2d5b5ea58adbf98e82
,可以看到这个commit id和我们在log里面看到的本次提交commit id是一致的。
objects/23/c6e2c6e5bd959951813965d6cc607dbbd28fee
objects/32/454a9530d7b66fd8b35d2d5b5ea58adbf98e82
objects/46/a2eb9234dc84c7b0c369afe067f6f1c3794500
objects/54/2123498f1065f5e063576f54acda8d535f5538
objects/9c/2ba432d595da5b3e004fb0a10232be67dc7937
之前我们提到objects文件夹里面存放的是历史版本库的文件。我们只增加了2个文件,但是objects里面却增加了5个文件。这是为什么呢?
我们在下一篇文章里将详细讲解刚才的提交操作在git仓库里都发生了什么事情。
下一篇:理解Git的存储结构设计(二)