探索 .git 目录,让你真正理解 git

简介:

探索 .git 目录,让你真正理解 git


“哇欧,我才读了 git 的快速入门指南就觉得它简直酷毙了,现在使用起 git 来感觉超舒服,妈妈再也不担心我会捅出什么篓子了。”—— 某位无名英雄曾曰过

新人刚使用 git 的时候,就像去到一个既不识当地文字也不会说当地语言的陌生的国家。只要你知道你在什么地方、要去哪里,一切都 OK,而一旦你迷路,麻烦就来了。

网上已经有许多关于学习基本的 git 命令的文章,但是本文不属于这一类,而是尝试另辟蹊径。

新手总是被 git 吓到,事实上也很难不被吓到。可以肯定的是 git 是很强大的工具但还不够友好。大量的新概念,有些命令用文件做参数和不用文件做参数各自执行的动作截然不同,还有隐晦的回馈等…

我以为克服第一道难关的方法就是不仅仅是使用 git commit/push 就完了。如果我们花点时间去真正了解到底git是由什么构造的,那将会省去不少麻烦。

初探 .git

那么我们开始吧。当你创建一个仓库的时候,使用 git init 指令, git 将会创建一个神奇的目录:.git。这个目录下包含了所有 git 正常工作所需要的信息。说白一点,如果你想从你的项目中删除 git 但是又要保留项目文件,只需要删除 .git 文件夹就可以了。但是,你确定要辣么做?


  
  
  1. ├── HEAD
  2. ├── branches
  3. ├── config
  4. ├── description
  5. ├── hooks
  6. ├── pre-commit.sample
  7. ├── pre-push.sample
  8. └── ...
  9. ├── info
  10. └── exclude
  11. ├── objects
  12. ├── info
  13. └── pack
  14. └── refs
  15. ├── heads
  16. └── tags

这就是你第一次提交之前 .git 目录的样子:

  • HEAD
    这个我们稍后会讨论
  • config
    这个文件包含你仓库的设置信息。例如这里会放你远程仓库的 URL,你的 email 地址,你的用户名等…。 每次你在控制台使用“git config…”指令时,修改的就是这里。
  • description
    gitweb(可以说是 github 的前身)用来显示仓库的描述。
  • hooks
    这是一个有意思的特性。Git 提供了一系列的脚本,你可以在 git 每一个有实质意义的阶段让它们自动运行。这些脚本就是 hooks,可以在 commit/rebase/pull…. 的前后运行。脚本的名字表示它什么时候被运行。例如一个有用的预推送 hook 可能会测试关于保持远程仓库一致性的式样原则。
  • info — exclude
    你可以把你不想让 git 处理的文件放到 .gitignore 文件里。那么,exclude 文件也有同样的作用,不同的地方是它不会被共享,比如当你不想跟踪你的自定义的 IDE 相关的配置文件时,即使通常情况下 .gitignore 就足够了(如果你用到了这个请在评论中告诉我)。

commit 的真相

每一次你创建一个文件并跟踪它会发现,git 会对其进行压缩然后以 git 自己的数据结构形式来存储。这个压缩的对象会有一个唯一的名字,即一个哈希值,这个值存放在 object 目录下。

在探索 object 目录前,我们先要问自己 commit 到底是何方神圣。commit 大致可以视为你工作目录的快照,但是它又不仅仅只是一种快照。

实际上,当你提交的时候,为创建你工作目录的快照 git 只做了两件事:

  1. 如果这个文件没有改变,git 仅仅只把压缩文件的名字(就是哈希值)放入快照。
  2. 如果文件发生了变化,git 会压缩它,然后把压缩后的文件存入 object 目录。最后再把压缩文件的名字(哈希值)放入快照。

这里只是简单介绍,整个过程有一点复杂,以后的博客里会作说明的。

一旦快照创建好,其本身也会被压缩并且以一个哈希值命名。那么所有的压缩对象都放在哪里呢?答案是object 目录。


  
  
  1. ├── 4c
  2. └── f44f1e3fe4fb7f8aa42138c324f63f5ac85828 // hash
  3. ├── 86
  4. └── 550c31847e518e1927f95991c949fc14efc711 // hash
  5. ├── e6
  6. └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // hash
  7. ├── info // let's ignore that
  8. └── pack // let's ignore that too

这就是我创建一个空文件 file_1.txt 并提交后 object 目录看起来的样子。请注意如果你的文件的哈希值是“89faaee…”,git 会把这个文件存在 “89” 目录下然后命名这个文件为 “faaee…”。

你会看到3个哈希。一个对应 file_1.txt ,另一个对应在提交时所创建的快照。那么第三个是什么呢?其实是因为 commit 本身也是一个对象并且也被压缩存放在 object 目录下。

现在,你需要记住的是一个 commit 包含四个部分:

  1. 工作目录快照的哈希
  2. 提交的说明信息
  3. 提交者的信息
  4. 父提交的哈希值

如果我们解压缩一个提交,你自己可以看看到底是什么:


  
  
  1. // by looking at the history you can easily find your commit hash
  2. // you also don't have to paste the whole hash, only enough
  3. // characters to make the hash unique
  4. git cat-file -p 4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

这是我看到的


  
  
  1. tree 86550c31847e518e1927f95991c949fc14efc711
  2. author Pierre De Wulf <test@gmail.com> 1455775173 -0500
  3. committer Pierre De Wulf <test@gmail.com> 1455775173 -0500
  4. commit A

如你所见我们得到了所期望看到的的:快照的哈希,作者,提交信息。这里有两样东西很重要:

  1. 正如预料的一样,快照的哈希 “86550…” 也是一个对象并且能在object目录下找到。
  2. 因为这是我的第一个提交,所以没有父提交。

那我的快照里面到底是些什么呢?


  
  
  1. git cat-file -p 86550c31847e518e1927f95991c949fc14efc711
  2. 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file_1.txt

到这里我们看到的最后一个对象是我们先前提到的唯一会存在于快照中的对象。它是一个 blob(二进制文件),这里就不作深究了。

分支,标签,HEAD 都是一家人

那么现在你知道 git 的每一个对象都有一个正确的哈希值。现在我们来看看 HEAD 吧!那么,在 HEAD 里又有什么呢?


  
  
  1. cat HEAD
  2. ref: refs/heads/master

这看起来 HEAD 不是一个hash,倒是容易理解,因为 HEAD 可以看作一个你目前所在分支的指针。如果我们看看 refs/heads/master,就会发现这些:


  
  
  1. cat refs/heads/master
  2. 4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

是不是很熟悉?是的,这和我们第一个提交的哈希完全一样。由此表明分支和标签就是一个提交的指针。明白这一点你就可以删除所有你想删除的分支和标签,而他们指向的提交依然在那里。只是有点难以被访问到。如果你想对这部分了解更多,请参考git book

尾声

到目前为止你应该了解到, git 所做的事就是当你提交的时候“压缩”当前的工作目录,同时将其和其他一些信息一并存入 objects 目录。但是如果你足够了解 git 的话,你就能完全控制提交时哪些文件应该放进去而哪些不应该放。

我的意思是,一个提交并非真正意义上是一个你当前工作目录的快照,而是一个你想提交的文件的快照。在提交之前 git 把你想提交的文件放在哪里? git 把他们放在 index 文件里。我们现在不会去深入探究 index,同时如果你确实好奇你可以参考这里

鸣谢

我希望通过本文你更好的理解了 git 的核心概念。如果你有任何问题和评论,请毫不犹豫的告诉我,你也可以在 twitter 上粉我。

下一次我们谈谈 git 的变基rebase,下次见,祝愉快。




本文来自云栖社区合作伙伴“Linux中国”

原文发布时间为:2013-04-02.

相关文章
|
开发工具 git
vscode编辑器里怎么显示.git隐藏文件夹到目录里
vscode编辑器里怎么显示.git隐藏文件夹到目录里
283 0
|
7月前
|
开发工具 git
深入探索Git的高级技巧与神奇操作(分支,高效合并)
深入探索Git的高级技巧与神奇操作(分支,高效合并)
352 0
|
存储 开发工具 git
.git 目录中有什么?
.git 目录中有什么?
59 2
|
存储 开发工具 git
git中怎样忽略.idea/文件和目录
git中怎样忽略.idea/文件和目录
202 0
|
开发工具 git
将本地目录推送到Git远程仓库
将本地目录推送到Git远程仓库
|
开发工具 git
git 排除已经提交的文件目录
如果你已经把一个文件夹提交到了 Git 仓库中,但是后来发现这个文件夹不应该被提交,可以按照以下步骤排除已提交的文件夹: 在文件夹的根目录下创建一个名为 .gitignore 的文件。 编辑 .gitignore 文件,添加需要排除的文件夹的名称,以及其他需要排除的文件或文件类型,每个名称占一行。 执行以下命令,将 .gitignore 文件提交到 Git 仓库中:
547 0
|
存储 开发工具 文件存储
Git托管项目的.git目录下都有什么?
我们在使用`git`托管项目代码时,如果是新建项目需要通过`git init`命令在项目根目录下初始化`.git`目录来实现后续的代码托管管理,如果直接从代码仓库拉取代码则会自动创建`.git`目录与远程仓库进行绑定。
|
开发工具 git
同事偷偷改了git目录让我跑不起项目
由于当年太年轻,资历不够深!!!导致文件命名不规范,现在想更正文件夹名字(common => COMMON),结果发现一个很奇特的现象!git 对文件(夹)名的大小写不敏感!!!!引发了一些问题。最终经过查阅资料得以解决。
同事偷偷改了git目录让我跑不起项目
|
开发工具 git
GIT上传服务器同步到web目录
GIT上传服务器同步到web目录
170 0
GIT上传服务器同步到web目录