git add与commit原理

在未接触git前,当需要修改已稳定运行的代码时,我们一般都是先把文件copy一份,在copy的文件上进行修改,比如a.py 修改成a1.py, 或者b.py修改成b_new.py等等,这个非常不便于管理及无法对修改历史进行追踪.

因而我们看看git在文件内容发生变化时是如何重新命名的.

新建一个空目录git-test

在目录下执行以下操作

$ git init

Initialized empty Git repository in /home/duan/hc-work/git-test/.git/

重点关注objects部分有无变化

$ tree .git/objects

├── objects

│ ├── info

│ └── pack

现在来看一下git add之后,看看有哪些变化:

$ echo hello > hello.txt

$ git add hello.txt

$ tree .git

这里面多了一个叫index的文件及.git/object多了一个文件

$ tree .git/objects/

.git/objects/

├── ce

│ └── 013625030ba8dba906f756967f9e9ca394464a

├── info

└── pack

采用git cat-file 来分别查看文件名为ce013625030ba8dba906f756967f9e9ca394464a的文件类型与文件内容,注意这里可以简写,只要不产生文件名冲突即可. 其次注意objects目录的下一级目录是以id的前两个字符命名的。

$ git cat-file -t ce01362503

blob

$ git cat-file -p ce01362503

hello

git是如何生成文件名013625030ba8dba906f756967f9e9ca394464a的,根据git说明操作如下:

$ wc -c hello.txt

6 hello.txt

$ sha1sum < <(printf "blob 6\000";cat hello.txt)

ce013625030ba8dba906f756967f9e9ca394464a

以上就是blob对象的文件名计算,通过文件长度+文件内容合并在一起计算sha1 id作为文件名.

再来看下带目录结构add时的变化:

$ mkdir -p a/b

$ ls

a hello.txt

$ echo 'hahaha!!!' > a/b/c.txt

$ git add .

$ tree .git/

├── objects

│ ├── 56

│ │ └── d90ab53ecd048a1f75f7e69ea3b276a4903725

│ ├── ce

│ │ └── 013625030ba8dba906f756967f9e9ca394464a

Git 并没有一个专门的暂存目录来存储一些代表文件变化的对象(Blobs),它使用一个叫做index的文件, 这里可以看出,index并不存储tree对象,只存储blob对象。

.git/index是一个二进制,感兴趣的话,可以从网上查到写入格式.

现在我们可以用命令行来展示index中的文件内容.

$ git ls-files -s

100644 56d90ab53ecd048a1f75f7e69ea3b276a4903725 0 a/b/c.txt

100644 ce013625030ba8dba906f756967f9e9ca394464a 0 hello.txt

$ git cat-file -t 56d90ab53ec

blob

$ git cat-file -p 56d90ab53ec

hahaha!!!

$ git commit -m 'first commit.'

[master (root-commit) bc9bbb6] first commit.

2 files changed, 2 insertions(+)

create mode 100644 a/b/c.txt

create mode 100644 hello.txt

提交之后,发现index并没有变化,也就是暂存区不变.

$ git ls-files -s

100644 56d90ab53ecd048a1f75f7e69ea3b276a4903725 0 a/b/c.txt

100644 ce013625030ba8dba906f756967f9e9ca394464a 0 hello.txt

但是objects多了几个文件

$ tree .git/objects/

.git/objects/

├── 56

│ └── d90ab53ecd048a1f75f7e69ea3b276a4903725

├── 57

│ └── 57ab9a5a5f1d2d78a1a3a8c1c8d7203b30947b

├── 9f

│ └── 07ce23710cc70e4ba9ef4ce0c62d132e87eda4

├── bc

│ └── 9bbb6ed25afbb48282557f489c6a5dbc71a282

├── ce

│ └── 013625030ba8dba906f756967f9e9ca394464a

├── d1

│ └── 8191dffd2b34ef45974cddcd3a6d05f9f747b9

├── info

└── pack

8 directories, 6 files

首先看到提交id 为bc9bbb6对应文件类型与内容

逐一分析这6个文件.

  • commit 文件对象

$ git cat-file -t bc9bbb6ed25a

commit

$ git cat-file -p bc9bbb6ed25a

tree d18191dffd2b34ef45974cddcd3a6d05f9f747b9

author dxp <cicido@126.com> 1678361997 +0800

committer dxp <cicido@126.com> 1678361997 +0800

first commit.

以上是commit文件对象的内容,commit id生成类似, 首先文件内容是确定的,包括tree id及作者信息及提交信息。

commit id计算

$ git cat-file -p bc9bbb6ed25a | wc -c

154

(base) duan@A5-14:~/hc-work/git-test$ sha1sum < <(printf "commit 154\000";git cat-file -p bc9bbb6ed25a)

bc9bbb6ed25afbb48282557f489c6a5dbc71a282 -

也是commit文件长度+commit内容生成sha1 id, 并以此id作为commit文件对象的文件名

  • tree文件对象

$ git cat-file -t d18191dff

tree

$ git cat-file -p d18191dff

040000 tree 9f07ce23710cc70e4ba9ef4ce0c62d132e87eda4 a

100644 blob ce013625030ba8dba906f756967f9e9ca394464a hello.txt

tree id 计算:

$ git cat-file -p d18191dff | wc -c

118

(base) duan@A5-14:~/hc-work/git-test$ sha1sum < <(printf "tree 118\000";git cat-file -p d18191dff)

d1662fd9ccfd6f7d4aee1d24c5b453f9ebf56d7b -

至此,commit, tree, blob对象的文件名id生成已全部清晰,都是由文件长度+文件内容生成.

  • 递归分析子tree id对象

$ git cat-file -t 9f07ce23710

tree

$ git cat-file -p 9f07ce23710

040000 tree 5757ab9a5a5f1d2d78a1a3a8c1c8d7203b30947b b

$ git cat-file -p 5757ab9a5a

100644 blob 56d90ab53ecd048a1f75f7e69ea3b276a4903725 c.txt

至此,通过tree文件内容能建立起整个代码目录结构,id生成过程是一个自下而上的过程.

  • blob文件对象

参照前面的blob文件id分析.

另外执行命令git write-tree可以得到最顶层的tree id,并用此id作为commit文件中的tree id.

$ git write-tree

d18191dffd2b34ef45974cddcd3a6d05f9f747b9

这个id刚好是提交对象中的tree-id. commit利用此id建立一个commit文件对象.

由此可见,git中共有四类对象blob, tree, commit, tag, tag对象将在后面进行介绍.

blob为文件对象,对应代码文件.

tree对目录对象,可以建立起整个代码目录结构。

commit对象,用于标记以当前提交时间整个代码目录与文件的结构。一旦生成提交对象,则可以通过此对象id,索引出整个代码结构及当前代码内容.

这样就可以一个commit id来代表某个时间结点时的所有目录与文件,代表了一个版本库.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值