这里通过一个实际的例子,展示如何使用底层命令直接操作git仓库然后获得期望的工作区

我们目标生成一个如下目录结构,同时使每个文件内的内容是其文件名(不包含扩展名)

1
2
3
├── dir
│ └── test2.txt
└── test1.txt

首先我们进入一个目录,我将其命名为demo,然后初始化一个git仓库

1
2
3
> mkdir demo
> cd demo
> git init

生成一个test1内容的对象

1
2
> echo "test1" | git hash-object -w --stdin
a5bce3fd2565d8f458555a0c6f42d0504a848bd5

将该对象加入暂存区并添加元信息

1
> git update-index --add --cacheinfo 100644 a5bce3fd2565d8f458555a0c6f42d0504a848bd5 test1.txt

以当前暂存区内容生成树对象

1
2
3
4
5
> git write-tree
c0da834e42dcbf7b2b1c4a97925bef105d3863a3

> git cat-file -p c0da834e42dcbf7b2b1c4a97925bef105d3863a3
100644 blob a5bce3fd2565d8f458555a0c6f42d0504a848bd5 test1.txt

通过给定的树对象创建提交对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> echo "first commit" | git commit-tree c0da834e42dcbf7b2b1c4a97925bef105d3863a3
f199922399cd67d4b3800820dc5f9619ef81f7cc

> git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test1.txt

Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: test1.txt

执行git log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> git log
fatal: your current branch 'master' does not have any commits yet

> git log 1cc24
commit f199922399cd67d4b3800820dc5f9619ef81f7cc
Author: demo <demo@gmail.com>
Date: Wed Dec 25 05:46:37 2024 -0500

first commit

> echo "f199922399cd67d4b3800820dc5f9619ef81f7cc" > .git/refs/heads/master
> git update-ref refs/heads/master f199922399cd67d4b3800820dc5f9619ef81f7cc

> git log
commit f199922399cd67d4b3800820dc5f9619ef81f7cc (HEAD -> master)
Author: demo <demo@gmail.com>
Date: Wed Dec 25 05:46:37 2024 -0500

first commit

添加test2.txt

1
2
3
4
> echo "test2" | git hash-object -w --stdin
180cf8328022becee9aaa2577a8f84ea2b9f3827

> git update-index --add --cacheinfo 100644 180cf8328022becee9aaa2577a8f84ea2b9f3827 test2.txt

这里我们为了制造一个悬垂对象,直接以当前暂存区创建树对象,更合理的做法是先git rm test1.txt,再构建树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
> git write-tree
81fa9d1c5348f86326ce5e7a86d11b54c8140d8d

> git reset --mixed
Unstaged changes after reset:
D test1.txt

> git read-tree --prefix=dir 81fa9d1c5348f86326ce5e7a86d11b54c8140d8d

> git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: dir/test1.txt
new file: dir/test2.txt

Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: dir/test1.txt
deleted: dir/test2.txt
deleted: test1.txt

> git restore --staged dir/test1.txt
> git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: dir/test2.txt

Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: dir/test2.txt
deleted: test1.txt

> git write-tree
63cc4ea9d7c4c3f9f51fcc05ee30bef9c1034480

> echo "second commit" | git commit-tree 63cc4 -p f1999
aac8d22b2a4ce4a30c9e39fef0ed7cdf19e29171

> git update-ref refs/heads/master aac8d22b2a4ce4a30c9e39fef0ed7cdf19e29171
> git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: dir/test2.txt
deleted: test1.txt

no changes added to commit (use "git add" and/or "git commit -a")

> git log
commit aac8d22b2a4ce4a30c9e39fef0ed7cdf19e29171 (HEAD -> master)
Author: demo <demo@gmail.com>
Date: Wed Dec 25 05:58:05 2024 -0500

second commit

commit f199922399cd67d4b3800820dc5f9619ef81f7cc
Author: demo <demo@gmail.com>
Date: Wed Dec 25 05:46:37 2024 -0500

first commit

> git reset --hard
HEAD is now at aac8d22 second commit

> tree
.
├── dir
│ └── test2.txt
└── test1.txt

1 directory, 2 files

移除中间创建的树对象

1
2
3
4
5
6
7
8
9
10
11
> git fsck --unreachable
Checking object directories: 100% (256/256), done.
unreachable tree 81fa9d1c5348f86326ce5e7a86d11b54c8140d8d

> git cat-file -p 81fa9d1c5348f86326ce5e7a86d11b54c8140d8d
100644 blob a5bce3fd2565d8f458555a0c6f42d0504a848bd5 test1.txt
100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test2.txt

> git prune
> git fsck --unreachable
Checking object directories: 100% (256/256), done.

此时完成在本地编辑该目录并进行git add git commit一系列等价操作