Git和SVN是咱们最经常使用的版本控制系(Version Control System, VCS),固然,除了这两者以外还有许多其余的VCS,例如早期的CVS等。顾名思义,版本控制系统主要就是控制、协调各个版本的文档内容的一致性,这些文档包括但不限于代码文件、图片文件等等。早期SVN占据了绝大部分市场,然后来随着Git的出现,愈来愈多的人选择将它做为版本控制工具,社区也愈来愈强大。相较于SVN,最核心的区别是Git是分布式的VCS,简而言之,每个你pull下来的Git仓库都是主仓库的一个分布式版本,仓库的内容彻底同样,而SVN则否则,它须要一个中央版本库来进行集中控制。采用分布式模式的好处即是你再也不依赖于网络,当有更改须要提交的时候而你又没法链接网络时,你只须要把更改提交到本地的Git仓库,最后有网络的时候再把本地仓库和远程的主仓库进行同步便可。固然,分布式和非分布式各有各的优缺点,可是目前来看,分布式的Git正逐渐被愈来愈多的人所接受并推广。本文主要对Git的基本原理和经常使用命令进行简介,试图从底层来讲明Git是如何工做的,从而帮助你们理解上层命令在执行的时候背后所产生的动做和变化。原理部分的内容能够参考Pro Git作进一步的了解,而经常使用的命令能够参考其余的资料。本文的总结根据本身的理解进行描述,若是错误,请不吝赐教。html
本质上,Git是一套内容寻址(content-addressable)文件系统,而和咱们直接接触的Git界面,只不过是封装在其之上的一个应用层。这个关系很有点相似于计算机网络中应用层和下属层的关系。在Git中,那些和应用层相关的命令(也就是咱们最经常使用的命令,如git commit、 git push等),咱们称之为porcelain命令(瓷器之意,意为成品、高级命令);而和底层相关的命令(几乎不会在平常中使用,如git hash-object、git update-index等),则称之为plumbing命令(管道之意,是链接git应用界面和git底层实现的一个管道,相似于shell,底层命令)。要了解Git的底层原理,就须要了解Git是如何利用底层命令来实现高层命令的。在此以前,让咱们先来看一下Git的目录结构,和各个文件在Git中的做用。node
在操做系统中,咱们的仓库就是一个文件夹。可是为何这些文件夹就是Git仓库呢?这是由于Git在初始化的时候会生成一个.git的文件夹,而Git进行版本控制所须要的文件,则都放在这个文件夹中。在桌面上新建一个目录,而后利用命令行在该目录下运行git init命令便可完成git仓库的初始化。若是这个时候你看不到.git目录,这是由于你的操做系统自动隐藏了该文件夹,须要在系统设置中设置隐藏文件可见。进入.git目录,即可以看到其中有不少的文件和文件夹,这每个文件都有各自的做用,下面结合图1来进行说明。git
图1 .git目录结构示意图web
在上图中,第一排的几个文件和文件夹是Git的核心,而第二排的则是一些不须要特别关注的。核心文件包括:config文件、objects文件夹、HEAD文件、index文件以及refs文件夹。下面依次对其进行说明。shell
config文件:该文件主要记录针对该项目的一些配置信息,例如是否以bare方式初始化、remote的信息等,经过git remote add命令增长的远程分支的信息就保存在这里;数据库
objects文件夹:该文件夹主要包含git对象。关于什么是git对象,将会在下一节进行详细介绍。Git中的文件和一些操做都会以git对象来保存,git对象分为BLOB、tree和commit三种类型,例如git commit即是git中的commit对象,而各个版本之间是经过版本树来组织的,好比当前的HEAD会指向某个commit对象,而该commit对象又会指向几个BLOB对象或者tree对象。objects文件夹中会包含不少的子文件夹,其中Git对象保存在以其sha-1值的前两位为子文件夹、后38位位文件名的文件中;除此之外,Git为了节省存储对象所占用的磁盘空间,会按期对Git对象进行压缩和打包,其中pack文件夹用于存储打包压缩的对象,而info文件夹用于从打包的文件中查找git对象;服务器
HEAD文件:该文件指明了git branch(即当前分支)的结果,好比当前分支是master,则该文件就会指向master,可是并非存储一个master字符串,而是分支在refs中的表示,例如ref: refs/heads/master。网络
index文件:该文件保存了暂存区域的信息。该文件某种程度就是缓冲区(staging area),内容包括它指向的文件的时间戳、文件名、sha1值等;数据结构
Refs文件夹:该文件夹存储指向数据(分支)的提交对象的指针。其中heads文件夹存储本地每个分支最近一次commit的sha-1值(也就是commit对象的sha-1值),每一个分支一个文件;remotes文件夹则记录你最后一次和每个远程仓库的通讯,Git会把你最后一次推送到这个remote的每一个分支的值都记录在这个文件夹中;tag文件夹则是分支的别名,这里不须要对其有过多的了解;app
除此之外,.git目录下还有不少其余的文件和文件夹,这些文件和文件夹会额外支撑一些其余的功能,可是不是Git的核心部分,所以稍做了解便可。hooks主要定义了客户端或服务端钩子脚本,这些脚本主要用于在特定的命令和操做以前或者以后进行特定的处理,好比:当你把本地仓库push到服务器的远程仓库时,能够在服务器仓库的hooks文件夹下定义post_update脚本,在该脚本中能够经过脚本代码将最新的代码部署到服务器的web服务器上,从而将版本控制和代码发布无缝链接起来;description文件仅供GitWeb程序使用,这里不须要过多的关心;logs则记录了本地仓库和远程仓库的每个分支的提交记录,即全部的commit对象(包括时间、做者等信息)都会被记录在这个文件夹中,所以这个文件夹中的内容是咱们查看最频繁的,无论是Git log命令仍是tortoiseGit的show log,都须要从该文件夹中获取提交日志;info文件夹保存了一份不但愿在.gitignore 文件中管理的忽略模式的全局可执行文件,基本也用不上;COMMIT_EDITMSG文件则记录了最后一次提交时的注释信息。从以上的描述中咱们能够发现,.git文件夹中包含了众多功能不一的文件夹和文件,这些文件夹和文件是描述Git仓库所必不可少的信息,不能够随意更改或删除;尤为须要注意的是,.git文件夹随着项目的演进,可能会变得愈来愈大,由于任何文件的任何一个变更,都须要Git在objects文件夹下将其从新存储为一个新的对象文件,所以若是一个文件很是大,那么你提交几回改动就会形成.git文件夹容量成倍增加。所以,.git文件夹更像是一本书,每个版本的每个变更都存储在这本书中,并且这本书还有一个目录,指明了不一样的版本的变更内容存储在这本书的哪一页上,这就是Git的最基本的原理。
上节中咱们讲到,Git分为porcelain命令和plumbing命令,而porcelain命令是基于plumbing来实现的。为了进一步的理解Git的底层原理,咱们将在这一节中详细的探讨Git对象的存储格式以及plumbing命令。若是把Git比做Linux操做系统,那plumbing命令就有点相似于shell命令,而上层的procelain命令即是利用shell命令编写的一系列的系统功能或工具,如你自定义的自动化运维工具等。在接下来的介绍中,咱们将试着如何利用plumbing命令,而不是porcelain命令,来完成Git的暂存和提交工做,并利用log查看提交记录。首先,咱们从Git的对象介绍开始。
在以前咱们提到过,Git是一套内容寻址(content-addressable)文件系统,那么Git是怎么进行寻址呢?其实,寻址无非就是查找,而Git采用HashTable的方式进行查找,也就是说,Git只是经过简单的存储键值对(key-value pair)的方式来实现内容寻址的,而key就是文件(头+内容)的哈希值(采用sha-1的方式,40位),value就是通过压缩后的文件内容。所以,在接下来的实践中,咱们会常常经过40位的hash值来进行plumbing操做,几乎每个plumbing命令都须要经过key来指定所要操做的对象。
Git对象的类型包括:BLOB、tree对象、commit对象。BLOB对象能够存储几乎全部的文件类型,全称为binary large object,顾名思义,就是大的二进制表示的对象,这种对象类型和数据库中的BLOB类型(常常用来在数据库中存储图片、视频等)是同样的,看成一种数据类型便可;tree对象是用来组织BLOB对象的一种数据类型,你彻底能够把它想象成二叉树中的树节点,只不过Git中的树不是二叉树,而是"多叉树";commit对象表示每一次的提交操做,由tree对象衍生,每个commit对象表示一次提交,在建立的过程当中能够指定该commit对象的父节点,这样全部的commit操做即可以链接在一块儿,而这些commit对象便组成了提交树,branch只不过是这个树中的某一个子树罢了。若是你能理解commit树,那Git几乎就已经理解了一半了。
Git对象的存储方式也很简单,基本能够用以下表达式来表示:
Key = sha1(file_header + file_content) Value = zlib(file_content) |
简单来讲,Git 将文件头与原始数据内容拼接起来,并计算拼接后的新内容的 40位的sha-1校验和,将该校验和的前2位做为object目录中的子目录的名称,后38位做为子目录中的文件名;而后,Git 用zlib的方式对数据内容进行压缩,最后将用 zlib 压缩后的内容写入磁盘。文件头的格式为 "blob #{content.length}\0",例如"blob 16\000",这种文件头格式也是常常采用的格式。对于tree对象和commit对象,文件头的格式都是同样的,可是其文件数据倒是有固定格式的,鉴于本次只是Git原理的基本介绍,这里再也不详细描述,有兴趣的能够去Git的官网查找相关文档进行了解;其实也能够本身按照理解构思一下,若是让你来设计这种格式,应该如何设计:tree对象相似于树中节点的定义,在tree对象中要包含对链接的BLOB对象的引用,而commit对象与tree对象相似,要包含提交的tree对象的引用,想到这里,我以为文档的阅读大概也就能够省去了。
在procelain命令中,为了将修改的文件加入暂存区(也叫索引库,将修改的文件key-value化,.git根目录下的index文件记录该暂存区中的文件索引),咱们会使用git add filename命令。那么在git add这个命令的背后,Git是如何使用plumbing命令来完成文件的索引操做呢?其实,git add命令对应着两个基本的plumbing命令:
git hash-object #获取指定文件的key,若是带上-w选项,则会将该对象的value进行存储 |
git update-index #将指定的object加入索引库,须要带上—add选项 |
所以,git add命令在plumbing命令中实际上是分红了两步:首先,经过hash-object命令将须要暂存的文件进行key-value化转换成Git对象,并进行存储,拿到这些文件的key;而后,经过update-index命令将这些对象加入到索引库进行暂存,这样便完成了Git文件的暂存操做。若是要根据Git对象的key来查看文件的信息,还须要涉及下面的一个plumbing命令:
git cat-file –p/-t key #获取指定key的对象信息,-p打印详细信息,-t打印对象的类型 |
利用该命令能够查看已经key-value化的Git对象的详细信息。
接下来,咱们利用plumbing命令来进行git add的实践。首先,新建一个Git仓库,经过在新建的文件夹中利用git init命令来初始化,这里再也不详述,以下图所示:
初始化以后,会在当前目录下生成.git目录,进入该目录,就会发现咱们上述的目录结构。而后,咱们新建一个version.txt文件并在文件中写入"version 1"字符串,这是version.txt的第一个版本,而后利用git hash-object –w命令将该文件转换为Git的对象并存储,以下图:
这里hash-objec命令会返回该Git对象的key值,这时到.git目录的objects目录下会发现,多了一个6c子目录,该目录中的文件名称为58b76a52188643965f3a6704166e8e0424b7fe,也就是该key值的后38位。记下该key值,由于咱们要根据该key值将该对象加入索引库。接着,咱们利用update-index命令进行索引化操做,以下图:
注意,这里必定要带上—add选项,而—cacheinfo选项则指出该文件的文件类型,100644表示普通文件,与之相关的还有可执行文件等等;而且,除了指定key值,还须要指定文件名,代表要把哪一个文件的哪一个版本加入索引库。该命令执行完成后,能够发现.git目录下多了index文件,而且在之后每次update-index命令执行以后,该index文件的内容都会发生变化。至此,git add的主要过程也便完成了。
这里咱们简单谈一下index文件。index是一个索引文件,存放的是暂存区的整个目录树的信息,而且为目录树中的每一个文件都保存了时间戳和长度。若是用UltraEdit打开使用过程当中的index文件,能够发现index的格式为如下形式:
Index魔数(DIRC) + 版本号 + 暂存的文件个数 + 每一个文件的时间戳和长度
Index索引库记录从项目初始化到目前为止,项目仓库中全部文件最后一次修改时刻的时间戳以及对应的长度信息,所以随着加入仓库中的文件不断增多,index文件也会不断增大。每次调用git add命令,都会把add的文件的索引信息(时间戳和大小)进行更新,而咱们所使用的git status命令,则会把每个文件的索引信息和上次提交的索引信息进行比较,若是发生了变化,就会显示出来。Pro git 中是这样描述暂存操做的:暂存操做会对每个文件计算校验和(即第一章中提到的 SHA-1 哈希字串),而后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域。意思很明确,也就是每一个文件对应的当前版本的key也会加入到index文件中,这个我没有进行验证,不过理论上讲应该是正确的。
在Git中,全部的内容以tree或者BLOB对象进行存储,若是把Git比做UNIX的文件系统,则tree对象对应于UNIX文件系统中的目录,而BLOB对象则对应于inodes或文件内容。在Git对象小节中,咱们大体猜测了tree对象的存储格式。其实,一个单独的tree对象包含一条或多条tree记录,每一条记录含有一个指向BLOB对象或子tree对象的sha-1指针(也就是一个40位的key值),并附有该对象的权限模式 、类型和文件名信息,所以,咱们的猜测也是八九不离十的。为何要建立tree对象呢?咱们都知道,在Git中,咱们add完已修改的文件以后,通常就直接commit暂存区中的内容到本地仓库了,彷佛并无tree这个概念。其实,建立tree对象只是add和commit中间的一个缓冲步骤,由于commit对象要根据tree对象来建立。那么如何建立tree对象呢?只须要以下命令便可:
git write-tree #根据索引库中的信息建立tree对象 |
该命令返回所建立的tree对象的key值,经过git cat-file能够查看该对象的详细信息。建立过程以下图:
从图中能够看出,cat-file –t显示该对象的类型为tree,代表该tree对象建立成功了,至此,树节点便建立完成了。
实际上,因为index暂存区包括了项目仓库中全部的文件,所以commit对象所对应的tree对象,永远都是工做目录的根tree对象。也就是说,每次commit,都是把工做目录的根目录所对应的tree对象,连接给这次的commit对象;并且,在Git中,每一个子目录都对应一个tree对象,每一个文件对应一个BLOB对象,所以整个工做目录对应一棵Git对象树,根节点就是commit对象所引用的tree节点,而每一个子文件夹又分别对应一棵子树。因此任何一个文件的更改,都会致使其上层全部父对象的更改和从新存储。这里再也不进行演示,你能够经过git add和git commit进行屡次提交,并在每次提交以后使用git log查看commit对象的key,使用cat-file获取对应的tree对象的key,并再次使用cat-file获取该tree对象下全部的子对象,这时你能够发现,子文件夹都对应一个tree节点,文件都对应一个BLOB节点。
在Git中,每一次commit都对应一个commit对象,而一个commit对象对应一个tree对象。为了建立commit对象,须要使用以下命令:
git commit-tree key –p key2 #根据tree对象建立commit对象,-p表示前继commit对象 |
该方法有点相似于数据结构中树的增长节点操做:都是向父节点中增长子节点。其中,-p选项指明了前继commit对象的key值,也就是父节点的key值,这样,这两个commit节点便链接在了一块儿,而不断的链接便构成了一棵树,也就是咱们接下来要讲的提交树。Commit对象的建立过程以下所示:
在该命令中,咱们只须要指定key的前六位便可,因为这是第一次提交,所以不须要带上-p选项来指明父节点。经过cat-file命令能够看到,commit对象已建立成功,该commit对象中包含了与之关联的tree对象的key值,以及author和committer的信息。若是要查看完整的提交记录,能够经过git log –stat key命令,该命令会打印指定commit对象以前的全部提交记录。至此,commit对象已经建立完成,而咱们也利用plumbing命令,完整的实现了Git的add和commit操做,Cool。到目前为止,所建立的全部对象的关系以下图所示:
图2 第一次提交后Git对象关系图
接下来,咱们在第一次提交的基础上完成第二次提交和第三次提交。第二次提交咱们会提交version.txt的第二个版本,并增长一个新的文件;第三次提交会演示在tree对象中构造子tree对象并提交。在下面的每一次提交中,咱们还须要指定每一次提交的前继提交对象,这样commit对象便链接在一块儿,造成一棵提交树。首先,咱们进行第二个版本的修改和提交。以下图,修改version.txt并添加一个new.txt文件,而后利用上面的方法进行key-value化和索引更新:
而后进行索引的更新:
而后咱们利用暂存区建立tree对象,并根据该tree对象建立commit对象,以下图所示。注意,本次commit须要利用-p选项指定这次commit对象的前继commit对象,能够看到,经过git log命令打印出来的commit对象,链接在了一块儿。
本次提交完成后,Git中的对象关系以下图所示:
图3 第二次提交后Git对象关系图
紧接着,咱们来进行第三次提交。首先,利用read-tree命令将第一个版本中的tree对象读入暂存区。以下图所示:
注意,在读取的过程当中,须要加上—prefix选项,不然没法成功读取,这是由于在index中相同路径的文件只能出现一次,因为version.txt已经存在于index索引库了,所以若是想把第一个版本的tree对象读取进来,须要将该版本的version.txt放在文件夹bak中。而后建立tree对象并进行第三次提交,以下图所示:
经过git log能够查看全部的commit对象。这个时候,经过cat-file命令查看这次建立的tree对象所包含的内容:
能够看到,所建立的tree对象还不只包括以上的两个BLOB对象,还包括刚才读取的子tree对象,这个时候若是把这个tree再导出成工做目录的话,则在根目录会多出一个bak子文件夹。通过第三次提交后,Git中的全部对象的关系如图4所示。
注意,这里加上这样的步骤只是为了让你们明白tree对象中的子tree对象的存在,正如上面上节所说的,整个工做目录对应一个tree对象,而且其下的每个子文件夹都是一个tree对象,每次的commit对象都对应着根tree对象,而任何一个对象的改变都会致使其上层全部tree对象的从新存储。
以上,即是咱们利用plumbing命令完成的三次提交的过程,但愿经过这几个步骤,能让你简单的理解porcelain命令和plumbing命令之间的联系,为接下来的Git学习作铺垫。
图4 第三次提交后Git对象关系图
本节的目的在于对Git中比较重要可是不太会常用的命令进行一个简要的介绍,从而让你们对Git中大部门命令都有一个总体的了解。Git中的基本命令的使用这里再也不赘述,总体的工做流程如图所示。若是对Git的分支还不是很了解的话,建议去仔细阅读下Pro Git的第三章。Git的基本工做流程如图5所示。其中,git pull、git push、git fetch、git remote等基本命令的使用这里再也不进行赘述,这些基本的命令是最重要的命令,请务必紧紧掌握。建议经过以上的基本原理的讲解和Pro Git的描述对各个基础命令背后所发生的变化进行详细的思考,以加深本身对Git应用层命令的认识。不要仅仅把本身局限于tortoiseGit的GUI的使用中,只有深刻的理解了工具,才有可能用好它。
本节重点对如下几个git命令进行介绍,重点在于对这些命令的基本使用的普及,包括:git log、git fork、git rebase、git reset、git reverse和git stash。大多数状况下,咱们在开发中小型项目的时候,若是团队成员不是不少,则只须要开一个分支就够了。在这种状况下,只要你操做规范,在push以前注意pull最新的代码,则基本不会出现比较严重的冲突或者问题,这时候以上命令基本都用不上,可是在多分支的状况下,咱们可能会使用以上的命令来进行分支合并或者版本回退等,所以,咱们有必要对这些命令作一个简单的了解,知道在何时去使用它们。
图5 Git的基本工做流程图
在提交了若干更新以后,又或者克隆了某个项目,想回顾下提交历史,可使用 git log 命令查看。默认不用任何参数的话,git log会按提交时间列出全部的更新,最近的更新排在最上面。通常状况下,我会使用以下命令来打印log中的提交日志记录:
git log --pretty=format:"%h %s" --graph |
其中。--pretty选项指定打印的格式,%h表示列出每一个提交对象的短的sha1值(40位中的前6位);--graph选项表示使用图的方式来打印日志记录。打印的结果以下图所示:
也可使用Git的GUI来显示Git的提交历史,在仓库中右键选择Git GUI,而后选择菜单栏上的 repository-->visual all branch history 选项,便可以显示全部分支的提交记录。以下图所示:
Git fork不是一个Git命令,而是一种工做流。它不是使用单个服务端仓库做为『中央』代码基线,而让各个开发者都有一个服务端仓库,这意味着各个代码贡献者有2个Git仓库而不是1个:一个本地私有的,另外一个服务端公开的,以下图所示。
Forking工做流的一个主要优点是,贡献的代码能够被集成,而不须要全部人都能push代码到仅有的中央仓库中。 开发者push到本身的服务端仓库,而只有项目维护者才能push到正式仓库。 这样项目维护者能够接受任何开发者的提交,但无需给他正式代码库的写权限。
把一个分支中的修改整合到另外一个分支的办法有两种,第一种是咱们经常使用的git merge操做,而第二种即是本节要讲的rebase(中文翻译为衍合)。该命令的原理是,回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,而后以基底分支(也就是主干分支master)最后一个提交对象(C4)为新的出发点,逐个应用以前准备好的补丁文件,最后会生成一个新的合并提交对象(C3'),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游。以下图所示:
通常咱们使用rebase的目的,是想要获得一个能在远程分支上干净应用的补丁,好比某些项目你不是维护者,但想帮点忙的话,最好用rebase:先在本身的一个分支里进行开发,当准备向主项目提交补丁的时候,根据最新的 origin/master 进行一次衍合操做而后再提交,这样维护者就不须要作任何整合工做(其实是把解决分支补丁同最新主干代码之间冲突的责任,化转为由提交补丁的人来解决),只需根据你提供的仓库地址做一次快进合并,或者直接采纳你提交的补丁。
在rebase的过程当中,也许会出现冲突。在这种状况,Git会中止rebase并会让你去解决冲突;在解决完冲突后,用git add命令去更新这些内容的索引, 而后,你无需执行git-commit,只要执行git rebase –continue,这样git会继续应用(apply)余下的补丁。若是要舍弃本次衍合,只须要git rebase --abort便可。切记,一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行rebase操做。
咱们在使用git pull命令的时候,可使用--rebase参数,即git pull --rebase。这里表示把你的本地当前分支里的每一个提交取消掉,而且把它们临时保存为补丁(这些补丁放到.git/rebase目录中),而后把本地当前分支更新为最新的origin分支,最后把保存的这些补丁应用到本地当前分支上。在使用tortoise的pull的过程当中,若是你留意tortoiseGit的日志的话,你就会发现,它使用的就是这种方式来pull最新的提交的。
在使用Git的过程当中,因为操做不当,做为初学者的咱们可能常常要去解决冲突。某些时候,当你不当心改错了内容,或者错误地提交了某些commit,咱们就须要进行版本的回退。版本回退最经常使用的命令包括git reset和git revert。这两个命令容许咱们在版本的历史之间穿梭。
下面就几种比较经典的场景进行总结:
场景1:当你改乱了工做区某个文件的内容,想直接丢弃工做区的修改时,用命git checkout -- filename;
场景2:当你不但改乱了工做区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了场景1,第二步按场景1操做;
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,使用git reset --hard commit_id,不过前提是没有推送到远程库。
穿梭前,用git log能够查看提交历史,以便肯定要回退到哪一个版本;要重返将来,用git reflog查看命令历史,以便肯定要回到将来的哪一个版本。
Git revert用来撤销某次操做,这次操做以前和以后的commit和history都会保留,而且把此次撤销做为一次最新的提交。git revert是提交一个新的版本,将须要revert的版本的内容再反向修改回去,版本会递增,不影响以前提交的内容。
Git revert和git reset均可以进行版本的回退,将工做区回退到历史的某个状态,两者有以下的区别:
git revert是用一次新的commit来回滚以前的commit,而git reset是直接删除指定的commit(并无真正的删除,经过git reflog能够找回),这是两者最显著的区别;
git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,可以抵消要被revert的内容;
在回滚这一操做上,效果差很少。可是在往后继续merge之前的老版本时有区别。由于git revert是用一次逆向的commit"中和"以前的提交,所以往后合并老的branch时,致使这部分改变不会再次出现;可是git reset是之间把某些commit在某个branch上删除,于是和老的branch再次merge时,这些被回滚的commit应该还会被引入。
Git stash用来暂存当前正在进行的工做, 将工做区还没加入索引库的内容压入本地的Git栈中,在须要应用的时候再弹出来。好比想pull 最新代码,又不想加新commit;或者为了修复一个紧急的bug,先stash,使返回到本身上一个commit,改完bug以后再stash pop,继续原来的工做。Git stash可让本地仓库返回到上一个提交状态,而本地的还未提交的内容则被压入Git栈。Git stash的基本使用流程以下:
git stash #暂存工做区还没有提交的内容 |
Do your work #在上一个提交的状态之上完成你的操做 |
git stash pop #将暂存的内容弹出并应用 |
当你屡次使用git stash命令后,你的栈里将充满了未提交的代码,这时候你会对将哪一个版本应用回来有些困惑,这时git stash list命令能够将当前的Git栈信息打印出来,你只须要将找到对应的版本号,例如使用 git stash apply stash@{1} 就能够将你指定版本号为stash@{1}的暂存内容取出来,当你将全部的栈都应用回来的时候,可使用git stash clear来将栈清空。TortoiseGit中的stash save菜单就对应该命令。
本文主要对Git的基本原理和经常使用命令进行介绍和知识普及。从Git的目录结构,到porcelain命令和plumbing命令,到利用plumbing命令完成commit实践,最后对一些比较重要的命令进行说明,但愿阅读完本文,你能对Git的原理有总体的认识,同时可以灵活的使用Git的各类命令。本文大多数内容来源于互联网,是一个知识收集和理解总结性的文章,但愿能真正帮助到你们