从例子看git的内部实现

零、问题
git每天提交保存的是完整修改后的内容,那么多次修改同一个文件,多次提交可能会生成多个版本。如果checkout特定版本需要依赖历史版本的话,那么此时回溯的时候需要判断特定文件是不是最后一次提交的,并且只保留最后一次修改的版本。
 
一、测试一次完整的提交包含了什么
1、1 先创建一个空的git目录
tsecer@harry: mkdir git.commit.what
tsecer@harry: cd git.commit.what
tsecer@harry: git init
Initialized empty Git repository in /home/tsecer/git.commit.what/.git/
tsecer@harry: 
 
1、2 创建20个空的文件并提交
tsecer@harry: for (( i = 0; i < 20; i++ )) do touch $i.txt; done
tsecer@harry: git add *.txt
tsecer@harry: git commit
Aborting commit due to empty commit message.
tsecer@harry: git commit -m "commit twenty"
[master (root-commit) 7d59bb2] commit twenty
 20 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 0.txt
 create mode 100644 1.txt
 create mode 100644 10.txt
 create mode 100644 11.txt
 create mode 100644 12.txt
 create mode 100644 13.txt
 create mode 100644 14.txt
 create mode 100644 15.txt
 create mode 100644 16.txt
 create mode 100644 17.txt
 create mode 100644 18.txt
 create mode 100644 19.txt
 create mode 100644 2.txt
 create mode 100644 3.txt
 create mode 100644 4.txt
 create mode 100644 5.txt
 create mode 100644 6.txt
 create mode 100644 7.txt
 create mode 100644 8.txt
 create mode 100644 9.txt
tsecer@harry: 
 
1、3 只修改其中一个文件然后提交并看日志
从这个输出来看,虽然只修改了一个文件,但是为该文件夹下的所有文件都生成了一个快照列表。
tsecer@harry: echo "hello git" > 0.txt
tsecer@harry: git add 0.txt
tsecer@harry: git commit -m "commit 0.txt only"
[master 6a19d25] commit 0.txt only
 1 file changed, 1 insertion(+)
tsecer@harry: cat .git/refs/heads/master 
6a19d259b2654577c8424b7e02e3748547e0375d
tsecer@harry: git cat-file -p `cat .git/refs/heads/master`
tree 38b4d8c2ff452462852dfacde2e05da8bb1cd50c
parent 7d59bb27266dd305dbf5152f45931a0ef5831b8d
author tsecer harry <tsecer@com> 1524800531 -0700
committer tsecer harry <tsecer@com> 1524800531 -0700
 
commit 0.txt only
tsecer@harry: git cat-file -p 38b4d8c2ff452462852dfacde2e05da8bb1cd50c
100644 blob 8d0e41234f24b6da002d962a26c2495ea16a425f 0.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 10.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 11.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 12.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 13.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 14.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 15.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 16.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 17.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 18.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 19.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 2.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 3.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 4.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 5.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 6.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 7.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 8.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 9.txt
tsecer@harry: 
 
二、修改文件夹时如何处理
2、1 创建子文件夹及文件
tsecer@harry: for (( i = 0; i < 2; i++ )) do mkdir  subdir$i; done
tsecer@harry: for (( i = 0; i < 2; i++ )) do echo i >  subdir$i/i.txt; done
tsecer@harry: git add subdir*
tsecer@harry: git commit -m "commit subdirectory"
[master 8977c23] commit subdirectory
 2 files changed, 2 insertions(+)
 create mode 100644 subdir0/i.txt
 create mode 100644 subdir1/i.txt
 
2、2 修改子文件夹内容并提交
tsecer@harry: echo "change subdir" > subdir0/i.txt 
tsecer@harry: git add subdir0/i.txt
tsecer@harry: git commit -m "change subdirectory"
[master df6a763] change subdirectory
 1 file changed, 1 insertion(+), 1 deletion(-)
 
2、3 查看提交内容
从这里看,本次提交的tree结构包含了整个根文件夹下所有的文件及文件夹的SHA1列表。
tsecer@harry: cat .git/refs/heads/master 
df6a763edaf3bc3c828e34ef6b61e59cc58e7cbe
tsecer@harry: git cat-file -p `cat .git/refs/heads/master`
tree dd3caffcd81ec3e7c0a3287259714ecb087c4d37
parent 8977c23ac54be58bc1319f4a90e32effce2011ef
author tsecer harry <tsecer@com> 1524810602 -0700
committer tsecer harry <tsecer@com> 1524810602 -0700
 
change subdirectory
tsecer@harry: git cat-file -p dd3caffcd81ec3e7c0a3287259714ecb087c4d37
100644 blob 8d0e41234f24b6da002d962a26c2495ea16a425f 0.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 10.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 11.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 12.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 13.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 14.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 15.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 16.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 17.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 18.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 19.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 2.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 3.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 4.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 5.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 6.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 7.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 8.txt
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 9.txt
040000 tree 48be8ac757f9598e1381d753acd249edf3323603 subdir0
040000 tree f74407b8c8ec3de56e12e7abaa5d687eac27517a subdir1
tsecer@harry: 
 
三、总结
由于git的代码并没有仔细看,所以大致猜测git对于"每次"提交的记录方法是记录了本次提交的"所有"根目录下文件的SHA1列表
这一点和SVN的记录方法有根本不同,对于前面的例子,假设我只修改了一个文件,那么svn可以只记录下这次提交的diff内容。直观上这种实现非常有诱惑性,因为它更加简洁高效,需要的存储空间少,查看本次提交的变化情况直接从repository上读取即可。
但是git采用的是一个空间消耗更大的实现方式:每次提交都将所有的根目录下文件都提取SHA1保存起来;而且存储空间也更大,因为对于文件来说,即使简单的在文件中添加一个空格,git也会将新文件完整的存储起来,并生成一个SHA1存储起来。
这样的话,其实每次checkout到一个特定版本的话,只要找到commit节点中的tree dd3caffcd81ec3e7c0a3287259714ecb087c4d37 信息即可,其中的parent 8977c23ac54be58bc1319f4a90e32effce2011ef对于生成该版本完整内容没有意义。保存此次的commit信息,只是为了查看提交的逻辑顺序。
git的这种实现的优点就是来切换到特定版本的时候,不需要查看上次提交的内容。
或许是随着计算机的发展,存储成本已经越来越低吧。因为没有真正用过git作为项目管理工具,所以这种实现的具体优点还没有体会到。
 
直观上理解,git的每次提交都是在当前的树形结构上追加一个commit节点,从而多次切换/提交形成一个复杂的提交树结构。
原文地址:https://www.cnblogs.com/tsecer/p/10487784.html