Git在版本控制或者说代码仓库领域已经成为当之无愧的王者,绝大多数的公司或者说团队,都在使用Git来维护自己的代码。因此,它已经成为软件工程师必须掌握的应用之一。 虽然Git在日常使用的时候,不需要很复杂的操作,只需要知道个大概,知道如何提交代码就行了,但是事实上,Git的功能非常复杂,命令繁多,想要全面掌握Git的方方面面,却绝非易事。 我自己所呆过的几个团队之中,说实话完完全全掌握Git的人几乎没有,因此在日常工作当中,偶尔碰到一些新人小白误操作或者GUI软件抽风之类的问题,即使是团队老人也要折腾许久才能搞明白状况,有时甚至挽救不回来,会发生丢失代码的情况(尽管肯定有办法能救回来,但就是没人会)。 我自己在这过程当中也是遭遇诸多问题,深感如果一有空当,一定要把Git的原理以及高级操作拿下。最近切换工作比较空闲,这就有了这篇文章。 注意:本文假定阅读者已经使用过Git并且已经掌握了基本概念和基本操作。

一、Git介绍

Git特性

Git之所以被人们采纳,成为主流的版本控制系统,肯定有其令人称赞的特点,现总结如下: - Git是分布式的版本控制系统,每个仓库里都包含着完整的内容,无论是开发者机器还是远程仓库,都是完整的一份,除非所有人的机器以及远端仓库服务器一同产生数据丢失,否则代码或者数据很难真的丢失掉 - Git直接记录快照,而不是做差异比较,从这点来说,Git仓库应当会比其他类型的仓库体积要大一些(同一份文件有多个副本),不过相信Git官方肯定做了文件压缩的优化,而且现在硬盘容量真的不算是瓶颈。 - Git几乎所有的操作都在本地执行,和传统的一些版本控制系统比起来,会感觉非常快速。除了推送和拉取代码等需要和远端交互的操作,绝大多数操作都是瞬间完成。 - Git通过SHA1哈希算法来索引文件,每个文件都有其独一无二的哈希值,因此Git能够保证文件的完整性不被破坏,如果在传输的过程中文件有损坏或者丢失的情况,Git能够立刻发现。 - Git一般只添加数据,Git很少会删除数据,无论你做什么操作,几乎都是在添加数据。虽然这样会令代码仓库的体积越来越大,但是也意味着所有的文件历史都完好无损地保存在代码仓库中,任何时候你都可以退回到代码仓库的任何状态,不必为自己修改了文件而无法回退担心。

Git三个区域

Git有三个区域,分别是Working Directory(工作目录)、Staging Area(暂存区域)、Repository(仓库区域)。 它们之间的关系如下图: {% asset_img areas.png %} - 红色的所谓工作目录,就是我们平时的项目文件夹了,比如我们的project文件夹下有个.git文件夹,那么project文件夹下除了.git文件夹以外的其他文件,都可以被认为是在工作目录中。当我们修改或新增了工作目录中的文件,就可以将其提交到暂存区中。 - 蓝绿色的所谓暂存区,会在我们使用git add xxx命令之后,在.git文件夹中的index文件中记录下一次提交的文件列表信息,同时,这些文件也会被暂存在.git文件夹下的Objects文件夹中。这个时候我们指定的文件就像被打上了标记,准备要提交了,当然提交的是Objects目录中的文件,并不再是工作目录中的文件了。即使再修改工作目录中的对应文件,提交时也不会生效。 - 最右边所谓的仓库区域,实际上是.git文件夹中的部分文件夹(主要是Objects和refs),就像名字所说,这才是真正的仓库区域,我们的代码仓库,全量的数据其实都存放在这些地方。当我们进行一次提交(commit)时,暂存区也就是index文件中记录的文件会被提交进仓库区域中。所以说平时我们只要把.git文件夹整个拷贝走,也可以在另一个地方轻松建立一个仓库(当然不推荐这么做)。

在Git中一个文件的状态流转

知道了区域还需要知道一个文件都有哪些状态,如下图: {% asset_img lifecycle.png %} 起初,所有新加的文件都处于未跟踪状态(Untracked)。当我们将文件加入暂存区时,文件就处于已暂存状态(Staged)。此时文件相当于被我们打上标记,告诉Git系统,我们要提交这个文件了。 然后,当我们使用commit命令去提交时,已暂存状态的文件就会被提交进仓库区域,此时文件就变成了未修改状态(Unmodified)。其实未修改状态的说法不完整,完整的说法应该是,已跟踪未修改状态。 当我们对已经跟踪未修改的文件再次修改时,文件就变成了已修改状态(Modified)。此时我们再将文件加入暂存区时,又变成了已暂存状态。当我们commit时,又会变成未修改状态

二、Git忽略文件的规则

规则

有时我们不希望项目文件夹里面的一些文件被Git关注到,希望Git忽略它们,并且最好在任何时候都不要再提醒我们这些文件修改了或者怎样怎样。 Git提供了一个.gitignore文件,你只需要在项目根目录,与.git目录同级的地方创建名为.gitignore文件就好。 然后这个文件里输入一些你想忽略的文件的匹配规则,比如:

*.[oa]
*~

第一行忽略所有以.o或者.a结尾的文件,第二行忽略所有以符号~结尾的文件。 文件.gitignore的格式规范如下: - 所有空行或者以#开头的行都会被Git忽略。 - 可以使用标准的glob模式匹配。 - 匹配模式可以以(/)开头防止递归。 - 匹配模式可以以(/)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

下面直接引用书中原话: > 所谓的glob模式是指shell所使用的简化了的正则表达式。 星号()匹配零个或多个任意字符; [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个a,要么匹配一个b,要么匹配一个c); 问号(?)只匹配一个任意字符; 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如[0-9]表示匹配所有0到9的数字)。 使用两个星号()表示匹配任意中间目录,比如a/**/z可以匹配a/z,a/b/za/b/c/z等。

异常情况

实际上,根本没这么简单。有一次我遇到了一种情况让我吐血好久才搞定。 我们假定目录是这个样子的:

/project
    -/resource
        -/testa
            someFile.txt
        -/testb

然后我遇到了这样的场景:我需要屏蔽掉resource目录下所有的文件,但是唯独保留其中一个:someFile.txt文件。 使用上述规则,我竟然硬是做不到,只要gitignore文件中存在/resource/或者/resource/*,那么无论我怎么使用感叹号,都没法把someFile.txt再包括进来。 谷歌查了很久,最终只找到如下解决方案:

/resource/*
!/resource/testa/
/resource/testa/*
!/resource/testa/someFile.txt

就是逐一地排除然后再并入,逐个目录深入进去,最终写到自己要包括的文件。如果有人能有更好的办法,请一定告诉我!