Git 是最流行的版本管理工具,也是程序员的必备技能之一。

即使天天使用它,很多人也未必了解它的原理。Git 为什么可以管理版本?git addgit commit这些基本命令,到底在做什么,你说得清楚吗?

一、初始化

首先,让我们创建一个项目目录,并进入该目录。

  1. $ mkdir git-demo-project
  2. $ cd git-demo-project
    我们打算对该项目进行版本管理,第一件事就是使用git init命令,进行初始化。
  1. $ git init
  2.  
  3. git init命令只做一件事,就是在项目根目录下创建一个.git子目录,用来保存版本信息。
  1. $ ls .git
  2. branches/
  3. config
  4. description
  5. HEAD
  6. hooks/
  7. info/
  8. objects/
  9. refs/

二、保存对象

  1. 接下来,新建一个空文件test.txt
  1. $ touch test.txt 然后,把这个文件加入 Git 仓库,也就是为test.txt的当前内容创建一个副本。
  1. $ git hash-object -w test.txt
  2. e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    上面代码中,git hash-object命令把test.txt的当前内容压缩成二进制文件,存入 Git。压缩后的二进制文件,称为一个 Git 对象,保存在.git/objects目录。
    这个命令还会计算当前内容的 SHA1 哈希值(长度40的字符串),作为该对象的文件名。下面看一下这个新生成的 Git 对象文件。
  1. $ ls -R .git/objects
  2. .git/objects/e6:
  3. 9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码可以看到,.git/objects下面多了一个子目录,目录名是哈希值的前2个字符,该子目录下面有一个文件,文件名是哈希值的后38个字符。

再看一下这个文件的内容。


  1. $ cat .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391

上面代码输出的文件内容,都是一些二进制字符。你可能会问,test.txt是一个空文件,为什么会有内容?这是因为二进制对象里面还保存一些元数据。

如果想看该文件原始的文本内容,要用git cat-file命令。


  1. $ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

因为原始文件是空文件,所以上面的命令什么也看不到。现在向test.txt写入一些内容。


  1. $ echo 'hello world' > test.txt

因为文件内容已经改变,需要将它再次保存成 Git 对象。


  1. $ git hash-object -w test.txt
  2. 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

上面代码可以看到,随着内容改变,test.txt的哈希值已经变了。同时,新文件.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad也已经生成了。现在可以看到文件内容了。


  1. $ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
  2. hello world

三、暂存区

文件保存成二进制对象以后,还需要通知 Git 哪些文件发生了变动。所有变动的文件,Git 都记录在一个区域,叫做"暂存区"(英文叫做 index 或者 stage)。等到变动告一段落,再统一把暂存区里面的文件写入正式的版本历史。

git update-index命令用于在暂存区记录一个发生变动的文件。


  1. $ git update-index --add --cacheinfo 100644 \
  2. 3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt

上面命令向暂存区写入文件名test.txt、二进制对象名(哈希值)和文件权限。

git ls-files命令可以显示暂存区当前的内容。


  1. $ git ls-files --stage
  2. 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt

上面代码表示,暂存区现在只有一个文件test.txt,以及它的二进制对象名和权限。知道了二进制对象名,就可以在.git/objects子目录里面读出这个文件的内容。

git status命令会产生更可读的结果。


  1. $ git status
  2. 要提交的变更:
  3. 新文件: test.txt

上面代码表示,暂存区里面只有一个新文件test.txt,等待写入历史。

四、git add 命令

上面两步(保存对象和更新暂存区),如果每个文件都做一遍,那是很麻烦的。Git 提供了git add命令简化操作。


  1. $ git add --all

上面命令相当于,对当前项目所有变动的文件,执行前面的两步操作。

五、commit 的概念

暂存区保留本次变动的文件信息,等到修改了差不多了,就要把这些信息写入历史,这就相当于生成了当前项目的一个快照(snapshot)。

项目的历史就是由不同时点的快照构成。Git 可以将项目恢复到任意一个快照。快照在 Git 里面有一个专门名词,叫做 commit,生成快照又称为完成一次提交。

下文所有提到"快照"的地方,指的就是 commit。

六、完成提交

首先,设置一下用户名和 Email,保存快照的时候,会记录是谁提交的。


  1. $ git config user.name "用户名"
  2. $ git config user.email "Email 地址"

接下来,要保存当前的目录结构。前面保存对象的时候,只是保存单个文件,并没有记录文件之间的目录关系(哪个文件在哪里)。

git write-tree命令用来将当前的目录结构,生成一个 Git 对象。


  1. $ git write-tree
  2. c3b8bb102afeca86037d5b5dd89ceeb0090eae9d

上面代码中,目录结构也是作为二进制对象保存的,也保存在.git/objects目录里面,对象名就是哈希值。

让我们看一下这个文件的内容。


  1. $ git cat-file -p c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
  2. 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt

可以看到,当前的目录里面只有一个test.txt文件。

所谓快照,就是保存当前的目录结构,以及每个文件对应的二进制对象。上一个操作,目录结构已经保存好了,现在需要将这个目录结构与一些元数据一起写入版本历史。

git commit-tree命令用于将目录树对象写入版本历史。


  1. $ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
  2. c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

上面代码中,提交的时候需要有提交说明,echo "first commit"就是给出提交说明。然后,git commit-tree命令将元数据和目录树,一起生成一个 Git 对象。现在,看一下这个对象的内容。


  1. $ git cat-file -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  2. tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
  3. author ruanyf 1538889134 +0800
  4. committer ruanyf 1538889134 +0800
  5. first commit

上面代码中,输出结果的第一行是本次快照对应的目录树对象(tree),第二行和第三行是作者和提交人信息,最后是提交说明。

git log命令也可以用来查看某个快照信息。


  1. $ git log --stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  2. commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  3. Author: ruanyf
  4. Date: Sun Oct 7 13:12:14 2018 +0800
  5. first commit
  6. test.txt | 1 +
  7. 1 file changed, 1 insertion(+)

七、git commit 命令

Git 提供了git commit命令,简化提交操作。保存进暂存区以后,只要git commit一个命令,就同时提交目录结构和说明,生成快照。


  1. $ git commit -m "first commit"

此外,还有两个命令也很有用。

git checkout命令用于切换到某个快照。


  1. $ git checkout c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

git show命令用于展示某个快照的所有代码变动。


  1. $ git show c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa

八、branch 的概念

到了这一步,还没完。如果这时用git log命令查看整个版本历史,你看不到新生成的快照。


  1. $ git log

上面命令没有任何输出,这是为什么呢?快照明明已经写入历史了。

原来git log命令只显示当前分支的变动,虽然我们前面已经提交了快照,但是还没有记录这个快照属于哪个分支。

所谓分支(branch)就是指向某个快照的指针,分支名就是指针名。哈希值是无法记忆的,分支使得用户可以为快照起别名。而且,分支会自动更新,如果当前分支有新的快照,指针就会自动指向它。比如,master 分支就是有一个叫做 master 指针,它指向的快照就是 master 分支的当前快照。

用户可以对任意快照新建指针。比如,新建一个 fix-typo 分支,就是创建一个叫做 fix-typo 的指针,指向某个快照。所以,Git 新建分支特别容易,成本极低。

Git 有一个特殊指针HEAD, 总是指向当前分支的最近一次快照。另外,Git 还提供简写方式,HEAD^指向 HEAD的前一个快照(父节点),HEAD~6则是HEAD之前的第6个快照。

每一个分支指针都是一个文本文件,保存在.git/refs/heads/目录,该文件的内容就是它所指向的快照的二进制对象名(哈希值)。

九、更新分支

下面演示更新分支是怎么回事。首先,修改一下test.txt


  1. $ echo "hello world again" > test.txt

然后,保存二进制对象。


  1. $ git hash-object -w test.txt
  2. c90c5155ccd6661aed956510f5bd57828eec9ddb

接着,将这个对象写入暂存区,并保存目录结构。


  1. $ git update-index test.txt
  2. $ git write-tree
  3. 1552fd52bc14497c11313aa91547255c95728f37

最后,提交目录结构,生成一个快照。


  1. $ echo "second commit" | git commit-tree 1552fd52bc14497c11313aa91547255c95728f37 -p c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  2. 785f188674ef3c6ddc5b516307884e1d551f53ca

上面代码中,git commit-tree-p参数用来指定父节点,也就是本次快照所基于的快照。

现在,我们把本次快照的哈希值,写入.git/refs/heads/master文件,这样就使得master指针指向这个快照。


  1. $ echo 785f188674ef3c6ddc5b516307884e1d551f53ca > .git/refs/heads/master

现在,git log就可以看到两个快照了。


  1. $ git log
  2. commit 785f188674ef3c6ddc5b516307884e1d551f53ca (HEAD -> master)
  3. Author: ruanyf
  4. Date: Sun Oct 7 13:38:00 2018 +0800
  5. second commit
  6. commit c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  7. Author: ruanyf
  8. Date: Sun Oct 7 13:12:14 2018 +0800
  9. first commit

git log的运行过程是这样的:

  1. 查找HEAD指针对应的分支,本例是master
  2. 找到master指针指向的快照,本例是785f188674ef3c6ddc5b516307884e1d551f53ca
  3. 找到父节点(前一个快照)c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
  4. 以此类推,显示当前分支的所有快照

最后,补充一点。前面说过,分支指针是动态的。原因在于,下面三个命令会自动改写分支指针。

  • git commit:当前分支指针移向新创建的快照。
  • git pull:当前分支与远程分支合并后,指针指向新创建的快照。
  • git reset [commit_sha]:当前分支指针重置为指定快照。
  1.  

Git 原理入门的更多相关文章

  1. Git原理入门简析

    为了获得更好的阅读体验,建议访问原地址:传送门 前言: 之前听过公司大佬分享过 Git 原理之后就想来自己总结一下,最近一忙起来就拖得久了,本来想塞更多的干货,但是不喜欢拖太久,所以先出一版足够入门的 ...

  2. Git原理入门解析

    前言: 之前听过公司大佬分享过 Git 原理之后就想来自己总结一下,最近一忙起来就拖得久了,本来想塞更多的干货,但是不喜欢拖太久,所以先出一版足够入门的: 一.Git 简介 Git 是当前流行的分布式 ...

  3. Git原理与命令大全

    Git (wiki: en  chs )是一个免费开源的分布式版本控制系统,由linux内核作者linus Torvalds开发,大型开源项目linux kernel.Android.chromium ...

  4. Git 快速入门--Git 基础

    Git 快速入门 Git 基础 那么,简单地说,Git 究竟是怎样的一个系统呢? 请注意接下来的内容非常重要,若你理解了 Git 的思想和基本工作原理,用起来就会知其所以然,游刃有余. 在开始学习 G ...

  5. Git【入门】这一篇就够了

    前言 欢迎关注公众号,白嫖原创PDF,也可以催更,微信搜:JavaPub,回复:[666] Git 在生产工作中是使用频率很高的工具,但我发现很多文章只是对它做了简单的提交命令说明,真正遇到 版本冲突 ...

  6. Git原理及常用操作命令总结

    git原理介绍及操作 git 原理——

  7. GIT 从入门到放弃大整理

    跟着廖雪峰学 GIT  http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 GUI f ...

  8. 第三章 Git的入门 - 读书笔记

    Android驱动月考3 第三章 Git的入门 - 读书笔记 对于Github,这是全世界最大的开源平台,你可以把你做的项目在这里开源,把你发现的一些新技术在这里开源,向全世界的开发者们分享,大家都彼 ...

  9. git简单入门

    git简单入门 标签(空格分隔): git git是作为程序员必备的技能.在这里就不去介绍版本控制和git产生的历史了. 首先看看常用的git命令: git init git add git comm ...

随机推荐

  1. 一、flex布局教程:语法篇

    本文转自阮一峰大师的教程,此处做记录,方面后面自己学习使用~   布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性.它对于那些特殊布局非常不方便 ...

  2. linux 安装源码后的操作 ldconfig

    https://blog.csdn.net/cqkxboy168/article/details/8657487 知识点: .如果使用 ldd 命令时没有找到对应的共享库文件和其具体位置,可能是两种情 ...

  3. WCF服务上应用protobuf z

    protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样 的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多.虽然是二进制数据格式,但并没有因此变得 ...

  4. Elasticsearch查询类型

    Elasticsearch支持两种类型的查询:基本查询和复合查询. 基本查询,如词条查询用于查询实际数据. 复合查询,如布尔查询,可以合并多个查询, 然而,这不是全部.除了这两种类型的查询,你还可以用 ...

  5. yii2.0中url重写实现方法

    在yii框架里有前台和后台页面,举例前台url重写. 控制器与路由 控制器以Controller作为后缀,继承自yii\web\Controller; 动作以action作为前缀,public访问修饰 ...

  6. Oracle 时段负载情况

    ALTER session SET nls_date_format='yyyy-mm-dd hh24:mi:ss'; SELECT *  FROM ( SELECT A.INSTANCE_NUMBER ...

  7. CIKM 2013推荐系统论文总结

    这几天在家没事,介绍几篇CIKM上关于推荐系统的文章, Personalized Influence Maximization on Social Networks Social Recommenda ...

  8. hibernate 映射 数据库number 映射为 double 为空 报错问题

    将 数据库表 映射成hibernate 实体类时 将number --- double 如果数据库中number 为空,查找数据时将报错 Can not set double field ***** ...

  9. Java虚拟机1:开篇

    1.前言 由于后期学习需要用到大量的JVM底层的东西,所有本人调整了一下学习计划,打算先从JVM入手,了解整个JAVA的运行机制,内存模型,编译原理等等一些底层的东西,这样在学习 后面的东西,会有一种 ...

  10. UVa 1636 - Headshot(概率)

    链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...