Git幕后的“故事”

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

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


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在背后到底做了什么:

  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”,当每个开发者都在自己的分支上提交时是不用担心任何东西的,因为你不会影响到其他人!

继续上面提交三次的那个例子,我们首先用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

现在继续看常用命令,脑海里一定从图的角度思考:

  • 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]下载分支代码
时间: 2024-10-17 20:15:15

Git幕后的“故事”的相关文章

Git历险记(二)——Git的安装和配置

各位同学,上回Git历险记(一)讲了一个 “hello Git” 的小故事.有的同学可能是玩过了其它分布式版本控制系统(DVCS),看完之后就触类旁通对Git就了然于胸了:也有的同学可能还如我当初入手Git一样,对它还是摸不着头脑. 从这一篇开始,我就将比较“啰嗦”的和大家一起从零开始经历Git使用的每一步,当然对我而言这也是一个重新认识Git的过程. 使用Git的第一步肯定是安装Git,因为在多数平台上Git是没有预装的.我平时主要的工作环境是windows和Linux(ubuntu),我想看

git文件致源码泄露

前言:在一道ctf题的驱动下,我进行了对git和.git文件致源文件泄露的学习. 一.任务 一道ctf题目. 二.确定题目考的点 谷歌关键词:版本管理工具 github ctf 由得到的结果猜测,可能考的点是.git文件导致源文件泄露. 三.对git的粗略学习 其实经常会用到git,例如在用hexo+github pages搭建博客的时候就用到了git,又如在linux下载一些工具的时候,用到的git clone. git:分布式版本控制系统. 首先,何为版本控制系统?以git的诞生故事来解释,

加速Java应用开发速度2——加速项目调试启动速度

上一篇Spring/Hibernate提升速度的文章主要是通过一些技巧来提升启动速度,还是做不到如类的热部署/热替换.因此再写一篇关于热部署/热替换的文章.之前也有很多人介绍过这些知识,不过比较分散,我写此篇的目的是聚合它们.本文以HotSpot虚拟机为例. 首先让我们来看两个概念:热部署.热替换 热部署 即在容器运行过程中,重新加载类或重新加载整个项目.常见的解决方案就是使用自定义ClassLoader: 部分加载的示例:如JSP.Play框架: 重新加载整个项目的示例:如Tomcat.Jet

No.1__C#

这是第一篇C#的日记,到现在为止已经学习了一个礼拜的C#了.由于是实习中才开始学习,所以这次不准备像在大学学习那样,拿着课本划重点,背概念.这应当是一门实践的课程,应该一边编程,一边学.这是到公司第一个礼拜,没有分配电脑,只能手动写程序了.说说这礼拜的进度吧,首先熟悉了一下vs2010.这个软件真是体积庞大,可以说是包罗万象,在win环境下的事,基本都能做了吧.买了一本head first c#,书上内容比较轻松,图片也比较多,没有一上来就罗列概念,而是随着例子,边做边讲,用到什么就讲什么.事实

揭秘资深米农“乡土老农”的域名投资之路

2014-11-17 微域名 [新朋友]点击标题下“微域名”关注. [老朋友]点击右上角分享给朋友. 在互联网世界,域名被称为“玉米”,相当于门牌号码:而国内从事域名注册与域名买卖的人,自然成了“米农”.近几年,动辄诱发江湖纷争.舆论围观的“米农”,自己却身形隐秘,极其低调.他们究竟如何呼风唤雨,操盘域名交易?这究竟是一门怎样的生意?一位资深“米农”对记者独家道出幕后的故事. 豪赌:“0531”两年涨6倍   “乡土老农”今年30多岁,供职于山东济南的一家媒体.“我大学专业跟IT有关,2002年

激情转型 三大战役重塑AMD

经过重组.加速和全面转型,AMD已经脱胎换骨.一个全新的面向未来的AMD,将兼顾传统PC业务与新兴业务市场,以创新的技术.独到的眼光和强大的执行力,直面云计算时代的挑战. 8月14日,包括AMD总裁兼首席执行官Rory Read.AMD首席运营官Lisa Su.AMD高级副总裁兼首席技术官Mark Papermaster在内的AMD首席高管团队首次集体访华,恰逢AMD成立45周年.大中华区成立十周年的大喜日子.经历了"后PC"时代带来的阵痛后,AMD在新管理团队的带领下已走出业务瓶颈.

深入了解Qt(三)之元signal和slot

深入了解Qt主要内容来源于Inside Qt系列,本文做了部分删改,以便于理解.在此向原作者表示感谢! 在Qt 信号和槽函数这篇文章中已经详细地介绍了信号和槽的使用及注意事项.在这里对其使用方面的知识就不再做介绍,只做一些相应补充. 我们知道信号和槽是用来在对象间通信的一种机制,当一个特定的事件发生时,signal会被emit,slot调用时用来响应响应的signal.signal和slot机制是类型安全的,signal和slot必须互相匹配(实际上一个slot参数可以比对应的signal的参数

【技术人员访谈数学王子】塞德里克&#183;维拉尼的多面人生

一个跟纯技术没啥关系的数学家访谈(特约记者是搞技术的,逃),很精彩,不顾一切分享给你们.前面是视频版,后面是文字版. 访谈视频地址:优酷.腾讯 访谈嘉宾 塞德里克·维拉尼(Cédric Villani),法国数学家,现任法国庞加莱研究所所长,法兰西科学院院士,在数理物理学(朗道阻尼和玻尔兹曼方程).最优输运理论和黎曼几何领域做出了重大贡献.2009年获费马奖,2010年获得菲尔茨奖. 特约记者 劳佳,硕士毕业于上海交通大学,现任SAP(美国)高级软件支持顾问.业余爱好语言.数学.设计,翻译著作有

Git历险记(四)——索引与提交的幕后故事

我想如果看过<Git历险记>的前面三篇文章的朋友可能已经知道怎么用git add,git commit这两个命令了:知道它们一个是把文件暂存到索引中为下一次提交做准备,一个创建新的提交(commit).但是它们台前幕后的一些有趣的细节大家不一定知晓,请允许我一一道来. Git 索引是一个在你的工作目录(working tree)和项目仓库间的暂存区域(staging area).有了它, 你可以把许多内容的修改一起提交(commit). 如果你创建了一个提交(commit),那么提交的一般是暂