Git幕后的“故事”

由于作操做系统实验的缘由,因此通读了一遍《Understanding git conceptually》,以为确实不错,因而就简单地记录一下。有的地方理解的还不是很深,可能不够准确,等抽时间好好读一下《Pro Git》。git

做者开篇说到:仅仅记住在何时用什么命令是不够的,出问题只是迟早的事。只有理解了Git的工做原理,才算真正学会Git。遗憾的是大部分网上的教程都只是教你在什么时候使用哪一个命令,而后让你去模仿。说得这么好,那就看看做者这篇教程是否把Git的工做原理讲清楚了。注意:如下1.2.1到1.2.3都是本地化操做,不要看到仓库、分支、合并等词语就觉得必定是远程操做了,1.2.4才会讲到多人协同开发时的远程操做。markdown


1.Commit Object和Head

使用Git的目的固然就是管理项目文件的版本变化,在Git中保存全部管理信息的数据结构叫作仓库(Repository)。能够在一个文件夹中经过命令git init建立仓库。仓库保存在项目文件目录下的一个叫作.git的文件夹中,它由两部分组成:数据结构

  • 提交对象(Commit object):反映项目状态的一组文件、对父提交对象的引用、当前提交对象的SHA-1签名构成了提交对象。父提交对象的引用使得仓库的提交对象造成了一个有向无环图,咱们能够一直遍历到第一次提交。每当咱们要查询或操做仓库,都应该以如何操做commit object图的角度去思考
  • 对提交对象的引用(Head):每一个Head都有个名字,默认每一个仓库都有个叫作master的Head。此外,指向当前活跃Head的引用叫作HEAD(二级引用)。

例如,下面就是提交三次后的对象图:分布式

---->  time  ----->

(A) <-- (B) <-- (C)
                 ^
                 |
               master
                 ^
                 |
                HEAD

咱们平常的工做流程通常以下,下面就从图的角度说明一下Git在背后到底作了什么:fetch

  1. 修改一些代码
  2. git log查看历史记录:显示从HEAD到初始提交的全部提交日志。用log命令显示出每一个提交对象的SHA-1签名,咱们就能控制HEAD的移动。
  3. git status查看修改文件列表:显示当前项目状态与HEAD发生变化的文件列表。
  4. git diff比较修改内容:显示当前项目状态与HEAD发生变化的文件内容。
  5. git commit -am "message"提交修改:建立提交对象,将HEAD做为父提交对象。提交完成后,HEAD将指向刚建立的新提交对象。-a参数至关于git add,自动将修改文件添加到提交列表里。

2.Branching

下面说说Git中的分支(Branch)。在Git中,Branch与Head几乎是等同的。每一个Branch都由一个Head来表示。因此,咱们用Branch指分支的Head以及它的全部父对象构成的整个历史,用Head指单独一个提交对象,通常是分支中最近的提交。Git分支的最佳实践是:用分支实现新特性,保证master(主干)始终处于可发布的状态。Git用户常常会说:”commits are cheap”,当每一个开发者都在本身的分支上提交时是不用担忧任何东西的,由于你不会影响到其余人!spa

继续上面提交三次的那个例子,咱们首先用git branch [new-head-name] [reference-to]命令新建一个Head。其中,HEAD^表示HEAD的父级,若是未提供提交对象的话,默认为HEAD。若是只是执行git branch则会列出全部Head:操作系统

git branch fix-headers HEAD^

(A) -- (B) ------- (C)
        |           |
   fix-headers    master
                    |
                   HEAD

要开始在新分支上工做,就要将新建立的Head设置为HEAD,能够经过git checkout [head-name]完成。注意:checkout不仅会修改HEAD的指向,它还会重写文件夹中的全部文件来匹配新HEAD指向提交对象所表示的项目状态。因此,checkout以前最好提交全部修改。切换完成后,再提交后对象图就变成了下面的样子:日志

+-------------- (D)
        /                 |
(A) -- (B) -- (C)         |
               | |
             master  fix-headers
                          |
                         HEAD

如今继续看经常使用命令,脑海里必定从图的角度思考:code

  • git checkout [branch_name]切换分支:切换HEAD指向的位置
  • git checkout -t [branch_name]新建分支

3.Merging

当你在分支上完成开发时就须要将改动Merge回master,命令就是git merge [head]git pull . [head]。假设当前Head叫作HEAD,分支Head叫作fix-headers,则Git的代码合并过程以下:对象

  1. 找到HEAD和fix-headers的共同祖先,假设叫ancestor,先看两种简单状况:
    1.1 若是ancestor是fix-headers,则什么都不作
    1.2 若是ancestor是HEAD,则执行fast-forward-merge
  2. 不然,比较出fix-headers在ancestor后的改动,将这些改动合并到HEAD
    2.1 若是没有冲突,则建立一个新的提交对象,以HEAD和fix-headers为父级,并将HEAD指向这个新对象,并更新项目文件
    2.2 若是有冲突,则不建立提交对象,插入冲突的标记,通知用户处理
fast-forward-merge的例子:
                +-- (D) -- (E)
               /            |
(A) -- (B) -- (C)           |
               |            |
            current     fix-headers
               |
              HEAD

执行`git merge fix-headers`合并以后的样子:
                +-- (D) -- (E)
               /            |
(A) -- (B) -- (C)           |
                            |
                    fix-headers, current
                                 |
                                HEAD
复杂状况下的合并例子:
         +---------- (D)
        /             |
(A) -- (B) -- (C) -------------- (E)
                      |           |
                 fix-headers    master
                                  |
                                 HEAD

执行`git merge fix-headers`合并以后的样子:
         +---------- (D) ---------------+
        /             |                  \
(A) -- (B) -- (C) -------------- (E) -- (F)
                      |                  |
                 fix-headers           master
                                         |
                                        HEAD

4.协同开发

前面讲到过:Git的重要特色就是Repository与项目文件是保存在一块儿的,因此Git能够在无需连网的状态下正常工做。可是这也意味着不一样的开发者在默认状况下是不共享Repository的。为了实现共享,Git使用分布式模型(Distribution Model)版本管理,既能够无中心化也能够有中心。

首先要访问你朋友的远程仓库就须要一个位置,叫作remote-specification,Git能够经过SSH、HTTP等协议对外提供访问。而后就能够经过git clone [remote-specification]下载远程仓库到本地了。除了简单的拷贝,Git会给远程Repository建立一个reference叫作origin,同时还会给每一个Head新建一个Head,名字以”origin/”开头来区分

远程仓库的样子:
                +---------------(E)
               /                 |
(A) -- (B) -- (C) -- (D)         |
                      |          |
                    master    feature
                      |
                     HEAD

你clone后的本地仓库是这个样子:
                +-------------- (E)
               /                 |
(A) -- (B) -- (C) -- (D)         |
                      |          |
     origin/master, master    origin/feature
                      |
                     HEAD

当远程仓库变化时,咱们能够经过git fetch [remote-repository-reference]命令将改动抓取到本地,生成对应的提交对象,再用前面讲过的git pull [remote-repository-reference] [remote-head-name]进行合并。其实,pull命令也会自动进行fetch,因此平时咱们直接使用pull就能够了。来看一个例子,假设你朋友在跟你一块儿开发,他本地的Repository变成了这个样子:

+-------- (E) -- (F) -- (G)
               /                         |
(A) -- (B) -- (C) -- (D) -- (H)          |
                             |           |
                           master     feature
                             |
                            HEAD

在你执行fetch以后你的仓库是这个样子:
                +------------ (E) ------------ (F) ---- (G)
               /               |                         |
(A) -- (B) -- (C) -- (D) --------------- (H)             |
                      |        |          |              |
                    master  feature origin/master  origin/feature
                      |
                     HEAD

注意:你的Head并未受任何影响,变化的只是带有"origin/"的Head。
如今就用pull命令合并,更新你的master和feature,完成后你的仓库就成了这个样子:
                +-------- (E) ------------- (F) ----- (G)
               /           |                           |
(A) -- (B) -- (C) -- (D) ------------ (H)              |
                           |           |               |
                        feature  origin/master,  origin/feature
                                     master
                                       |
                                      HEAD

与之相反,将本地修改发送到远程仓库使用git push [remote-repository-reference] [remote-head-name],远程仓库会负责提交对象的建立以及Head的合并移动等工做。要注意的是:向远程仓库push时,要求必须是fast-forward合并。

整理一下这部分的经常使用命令:

  • git pull [remote-repository-reference] [remote-head-name]下载分支代码
相关文章
相关标签/搜索