$ mkdir git-playground && cd git-playground $ git initInitialized empty Git repository in path/to/git-playground/.git/ $ ls .git HEAD config description hooks info objects refs
这是 Git 存储所有 commit 和其他用于操作这些 commit 相关信息的地方。当你克隆一个仓库的时候就是复制这个目录到你的文件夹,为仓库里的每一个分支创建一个远程跟踪分支,并根据 HEAD 文件检出一个初始的分支。我们将在稍后讨论在 Git 架构中 HEAD 文件的用途,但是这里需要记住的就是克隆一个仓库本质上就是仅仅从别的地方复制一份 .git 目录。
这就是我们应用修改的基准。你对 index 文件所做的所有修改在将树写入仓库之前都是暂时的。然而你添加的对象是立刻写入到仓库的。如果你放弃当前对树的修改,这些对象稍后会被垃圾回收搜集并删除。 这意味着如果你不小心丢弃了对某一个文件的修改,在 git 运行 GC 之前是可以恢复的。垃圾回收通常发生在有太多的未引用对象时才发生。
我们来删除工作目录中的两个文件:
$ rm f1.txt f2.txt
如果我们运行git status 我们会看到以下信息:
$ git status On branch master Initial commit Changes to be committed: (use"git rm --cached ..." to unstage) new file: f1.txt new file: f2.txt Changesnot staged for commit: (use"git add/rm ..." to update what will be committed) (use"git checkout -- ..." to discard changes in working directory)
deleted: f1.txt deleted: f2.txt
信息有点多。有两个文件被删除、两个新文件同时还是 “Initial commit”。我们来看看为什么。当你运行 git status 时,git做了两个比较:
将 index 文件和当前的工作目录比较 —变化是 “not staged for commit”
将 index 文件和 HEAD 提交比较 —变化是 “to be committed”
所以在这里我们看到 git 将两个已删除的文件报告为 “Changes not staged for commit”,我们已经知道这个信息是怎产生的—它将当前的工作目录和 index 文件比较发现工作目录丢失两个文件(因为我们刚才删除了)。
我们同时还看在 “Changes to be committed” 下面 git 报告了了两个新文件。这是因为到目前为止我们的仓库中还没有任何提交,所以这个 HEAD 文件(我们稍后做详细的解释)指向一个所谓的“空树”对象(没有任何文件)。所以 Git 以为我们刚刚创建了一个新的仓库,所以为什么它显示 “Initial commit”,并将 index 文件中的所有文件都当做新文件。
现在如果我们执行 git add . 它将修改 index 文件(删除了两个文件),然后再次执行 git status 就会显示没有任何修改,因为现在我们的工作目录和 index 文件中都没有文件:
$ git add . $ git statusOn branch master Initial commit nothing to commit (create/copy files anduse"git add" to track)
我们继续通过创建新文件 f3.txt 来创建一个新的树。
$ echo ‘f3 content’> f3.txt $ git add f3.txt
如果现在运行 git status:
$ git status On branch master Initial commit Changes to be committed: (use"git rm --cached ..." to unstage) new file: f3.txt
我们发现检查到了一个新文件。同样,这个修改是报告在 “Changes to be committed” 下,所以现在 Git 是将 index 文件和 “空树” 作比较。所以认为 index 文件中已经有了新的文件 blob。我们来确认一下:
$ git ls-files -s 1006445927d85c2470d49403f56ce27afd8f74b1a425890 f3.txt # Save the hash of the f3.txt file blob $ F3CONTENT_BLOB_HASH=5927d85c2470d49403f56ce27afd8f74b1a42589
好了,index 的结构是正确的,我们现在可以通过这个 index 在仓库中创建一个树。我们通过 write-tree 命令来完成:
$ LATEST_TREE_HASH=$( git write-tree )
很棒。我们刚才通过 index 创建了一个树。并且将新的树的哈希值存到了 LATEST_TREE_HASH 变量。我们已经通过手动将 f3 content blob 写入到仓库并且通过 mktree 来创建了一个树,但是使用 index 文件更方便。
有趣的是如果你现在运行 git status 你会发现git 仍然认为存在一个新文件 f3.txt:
$ git status On branch master Initial commit Changes to be committed: (use"git rm --cached ..." to unstage) new file: f3.txt
$ git checkout master Switched to branch 'master' $ git log --pretty=oneline [some hash] latest commit [some hash] first commit $ ls -l f3.txt
一起来看看另外一个 forked 分支:
$ git checkout forked Switched to branch 'forked' $ git log --pretty=oneline f30305a8a23312f70ba985c8c644fcdca19dab95 forked commit
f30305a8a23312f70ba985c8c644fcdca19dab95 initial commit
$ git ls
f1.txt f2.txt $ cat f1.txt I am modified f1 content
一个 tag 就是指向某一个 commit 的文本文件
你兴许已经知道除了使用分支(一条工作线的)还可以使用 tag 来跟踪单独的 commit。Tag 通常用于标记重要的开发节点如版本发布。现在我们的仓库中有3个 commit。我们可以使用 tag 来给它们命名。和分支一样,一个 tag 就是一个文本文件,它包含了一个 commit 的哈希值,同样也是引用组的一部分。