1

Git是当下使用率最高的版本控制管理工具,而且越来越多能的项目都开始采用git作为源码管理工具,掌握如何使用Git基本上快成为一个开发人员的标配技能。要更好的在工作中高效的使用git、处理遇到git问题,就很有必要熟悉了解下Git的内部工作的基本原理。本文通过一个新创建的Git本地仓库来揭示Git的内部存储结构设计。

本文示例的环境基于Windows 10。使用到的工具如下:

(下载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内部是有差异的。随后会介绍。

我们用一张图来总结:
Git概念结构图
<center>图-1</center>

接下来我们用一个实例来观察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 '<?php\necho "Hello World!"\n?>' > 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 <efreykong@outlook.com> 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的存储结构设计(二)


efrey
13 声望2 粉丝