Fork me on GitHub

分布式管理系统 Git

一、Git基础

1.1 环境配置

1.2 基本操作

1.3 分支

1.4 标签

1.5 补丁

先占个坑。

二、知识点

基本命令让你快速的上手使用Git,知识点能让你更好的理解Git。

2.1 文件的几种状态

理解这几种文件状态对于理解 Git 是非常关键的(至少可以看懂一些错误提示了)。

2.2 快照和差异

详细可看:Pro Git: Git基础中有讲到 直接记录快照,而非差异比较,这里只讲我个人的理解。

Git 关心的是文件数据整体的变化,其他版本管理系统(以svn为例)关心的某个具体文件的差异。这个差异是好理解的,也就是两个版本具体文件的不同点,比如某一行的某个字符发生了改变。

Git 不保存文件提交前后的差异,不变的文件不会发生任何改变,对于变化的文件,前后两次提交则保存两个文件。举个例子:

SVN:

  1. 新建3个文件a, b, c,做第一次提交 -> version1 : file_a file_b file_c
  2. 修改文件 b, 做第二次提交(真正提交的是 修改后的文件 b 和修改前的 file_b 的 diff) -> version2: diff_b_2_1
  3. 当我要 checkout version2 的时候,实际上得到的是 file_a file_b+diff_b_2_1 file_c

Git:

  1. 新建3个文件a, b, c,做第一次提交 -> version1 : file_a file_b file_c
  2. 修改文件 b (得到file_b1), 做第二次提交 -> version2: file_a file_b1 file_c
  3. 当我要用 version2 的时候,实际上得到的是 file_a file_b1 file_c

上面的 file_a file_b1 file_c 就是 version2 的 快照

2.3 Git数据结构

Git的核心数是很简单的,就是一个链表(或者一棵树更准确一些?无所谓了),一旦你理解了它的基本数据结构,再去看Git,相信你有不同的感受。继续用上面的例子(所有的物理文件都对应一个 SHA-1 的值)

当我们做第一次提交时,数据结构是这样的:

sha1_2_file_map:
    28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a
    ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b
    1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c

commit_26b985d269d3a617af4064489199c3e0d4791bb5:
    base_info:
        Auther: "JerryZhang(chinajiezhang@gmail.com)"
        Date: "Tue Jul 15 19:19:22 2014 +0800"
        commit_content: "第一次提交"
    file_list:
        [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
        [2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9
        [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
        pre_commit: null
    next_commit: null

当修改了 file_b, 再提交一次时,数据结构应该是这样的:

sha1_2_file_map:
    28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a
    ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b
    1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c
    39015ba6f80eb9e7fdad3602ef2b1af0521eba89 => file_b1

commit_26b985d269d3a617af4064489199c3e0d4791bb5:
    base_info:
        Auther: "JerryZhang(chinajiezhang@gmail.com)"
        Date: "Tue Jul 15 19:19:22 2014 +0800"
        commit_content: "第一次提交"
    file_list:
        [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
        [2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9
        [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
    pre_commit: commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff
    next_commit: null

commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff:
    base_info:
        Auther: "JerryZhang(chinajiezhang@gmail.com)"
        Date: "Tue Jul 15 22:19:22 2014 +0800"
        commit_content: "更新文件b"
    file_list:
        [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
        [2]: 39015ba6f80eb9e7fdad3602ef2b1af0521eba89
        [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
    pre_commit: null
    next_commit: commit_26b985d269d3a617af4064489199c3e0d4791bb5

当提交完第二次的时候,执行 git log,实际上就是从 commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff 开始遍历然后打印 base_info 而已。

实际的 git 实际肯定要比上面的结构((的信息)的)要复杂的多,但是它的核心思想应该是就是,每一次提交就是一个新的结点。通过这个结点,我可以找到所有的快照文件。再思考一下,什么是分支?什么是 Tags,其实他们可能只是某次提交的引用而已(一个 tag_head_node 指向了某一次提交的node)。再思考怎么回退一个版本呢?指针偏移!依次类推,上面的基本命令都可以得到一个合理的解释。

理解git fetch 和 git pull的差异

上面我们说过 git pull 等价于 git fetchgit merge 两条命令。当我们 clone 一个 repo 到本地时,就有了本地分支和远端分支的概念(假定我们只有一个主分支),本地分支是 master,远端分支是 origin/master。通过上面我们对 Git 数据结构的理解,masterorigin/master 可以想成是指向最新 commit 结点的两个指针。刚 clone 下来的 repo,masterorigin/master 指针指向同一个结点,我们在本地提交一次,origin 结点就更新一次,此时 masterorgin/master 就不再相同了。很有可能别人已经 commit 改 repo 很多次了,并且进行了提交。那么我们的本地的 origin/master 就不再是远程服务器上的最新的位置了。 git fetch 干的就是从服务器上同步服务器上最新的 origin/master 和一些服务器上新的记录/文件到本地。而 git merge 就是合并操作了(解决文件冲突)。git push 是把本地的 origin/mastermaster 指向相同的位置,并且推送到远程的服务器。

理论部分是我个人对 Git 的理解,难免有偏差,看看就可以了。