Git学习笔记 - git 原理

####开篇

接触git时间也不短了,看了不少资料,但是在使用过程中还是经常遇到问题,究其原因,是因为对git的用法以及使用场景理解不够透彻,而且git知识点繁杂,时间一长,容易忘。于是决定静下心来,认认真真的对git的用法做一些归纳总结。

首先推荐一本书《Pro Git》网页地址在这里,这本书讲的很清晰,而且全面。接下来我就直接进入主题,该篇主要概况一下Git的原理,主要是为了理解Git,如果想详细了解其内部原理,可以看这里

####Git原理

#####Git特点

  • Git直接记录快照,而非差异比较
  • Git采用分布式,而非集中式,本地仓库
  • Git强大的分支特性,高效迅速
  • Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。所有保存在 Git 数据库中的内容都是用此哈希值来作索引的,而不是靠文件名。

#####文件的三种状态
对于任何一个文件,在 Git 仓库只有三种状态:

  • 已修改(modified) //表示修改了某个文件还没有放到暂存区
  • 已暂存(staged) //表示已放到暂存区,但没有放到版本区
  • 已提交(committed) //表示该文件已经放到版本区

#####文件的三个区域:

  • 工作区 (工作区就是指项目中的源码文件目录,这些文件实际上都是从 Git 目录中的压缩对象数据库中提取出来的)
  • 暂存区 (暂存区可以理解成.git目录下的index文件,里边是对object的索引)
  • 版本区 (版本区可以理解成.git目录下的HEAD文件,里边也是对object的索引)

#####git原理图

下面用一张图进行说明,该图是仿照这篇文章里的插图用OmniGraffle重新画的,根据自己理解做了略微修改。

git-flowchart

  • 图中左侧为工作区,右边为仓库区,仓库区又分为暂存区(staged)和版本区(committed),暂存区用index标识,版本区用HEAD标识。
  • 图中的 objects 标识的区域为 Git 的对象库,实际位于 “.git/objects” 目录下
  • 当对工作区修改(或新增)的文件执行 “git add” 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID 被记录在暂存区的文件索引中。
  • 当执行提交操作git commit时,暂存区的目录树写到版本库(对象库)中,HEAD会做相应的更新。即HEAD指向的目录树就是提交时暂存区的目录树。
  • 当执行 git reset HEAD <file> 命令时,暂存区的目录树会被重写,被HEAD指向的目录树所替换,但是工作区不受影响。
  • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
  • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。
  • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

由图可知,可以改变暂存区的操作有:

  • git add <path>会将working directory中的内容添加进入git index
  • git reset HEAD <path>会将git index中path内容删除,重新放回working directory中
  • checkout HEAD <file>会将HEAD对应分支中file替换暂存区

可以改变工作区的操作有:

  • checkout -- <file>会用暂存区全部或指定的文件替换工作区的文件
  • checkout HEAD <file>会将HEAD对应分支中file替换暂存区

从图中也可以看出checkout HEAD <file>等同于git reset HEAD <path>checkout -- <file>

此外着重注意一下命令之间的差别,比如checkout -- <file>checkout HEAD <file>,前者是暂存区到工作区,后者是版本区到工作区,还有git reset HEAD <path>git reset HEAD,前者是暂存区对应文件的重置,此时版本区不会重置,后者暂存区和版本区同时重置,对于 git reset –soft/hard/mixed 这几种模式 也是针对后者来说的。

那么在实际操作过程中怎么查看当前文件的状态呢,没错,git status

####git status

平时在用git的时候我们要经常通过git status命令来查看当前状态,这里记录一下

1
2
3
4
5
6
7
$ git init
$ git status
On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)

可以看到仓库中没有track任何文件。并且最后一行提示通过创建或者拷贝文件到工作目录,并执行 git add 进行文件追踪。

1
2
3
4
5
6
7
8
9
10
11
12
$ touch README
$ git status
On branch master

Initial commit

Untracked files:
(use "git add <file>..." to include in what will be committed)

README

nothing added to commit but untracked files present (use "git add" to track)

可以看到新建的README文件出现在“Untracked files”下面。未跟踪的文件意味着在之前的快照(提交)中没有这些文件,接下来

1
2
3
4
5
6
7
8
9
10
$ git add README
$ git status
On branch master

Initial commit

Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: README

只要在 “Changes to be committed” 这行下面的,就说明是已暂存状态。(译注:其实 git add 的潜台词就是把目标文件快照放入暂存区域,也就是 add file into staged area,同时未曾跟踪过的文件标记为需要跟踪。这样就好理解后续 add 操作的实际意义了。)接下来在README中添加一些代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ echo "hello" > README
$ git status
On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: README

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: README

可以看到README文件出现两个状态,一个是已暂存(Changes to be committed),一个是未暂存(Changes not staged for commit),接下来执行 git commit

1
2
3
4
$ git commit -m "first commit"
[master (root-commit) fb82a3d] first commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README

会将已暂存的状态提交到仓库中,接下来:

1
2
3
4
5
6
7
$ git add README
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: README

可以看到此时的README处于已暂存状态,再提交一下代码

1
2
3
4
5
6
$ git commit -m "modify README"
[master e43d7b6] modify README
1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working directory clean

可以看到,此时README文件处于已提交状态,工作目录相当干净。

通过以上操作,我们学会了怎样通过git status的打印信息来判断当前文件的状态。

  • 当出现 Untracked files:提示时,我们需要通过git add命令将那些没有被追踪的文件添加进来.
  • 当出现 Changes not staged for commit:提示时,我们同样需要通过git add命令将文件的未暂存状态转为暂存状态。
  • 只有出现了Changes to be committed:,我们才可以通过git commit命令将文件已暂存状态转为已提交状态。这样才算真正的将文件提交到git仓库。

状态看明白了,如何比较工作区,暂存区,版本区之间的差异呢,没错,git diff

####git diff

git diff可以比较working tree同index之间,index和git directory之间,working tree和git directory之间,git directory中不同commit之间的差异

  • git diff [<path>...]这个命令最常用,在每次add进入index前会运行这个命令,查看即将add进入index时所做的内容修改,即working directory和index的差异。
  • git diff --cached [<path>...]这个命令初学者不太常用,却非常有用,它表示查看已经add进入index但是尚未commit的内容同最后一次commit时的内容的差异。即index和git directory的差异。
  • git diff --cached [<commit>] [<path>...]这个命令初学者用的更少,也非常有用,它表示查看已经add进入index但是尚未commit的内容同指定的<commit>之间的差异,和上面一条很相似,差别仅仅<commit>,即index和git directory中指定版本的差异。
  • git diff <commit> [<path>...]这个命令用来查看工作目录和指定<commit>的commit之间的差别,如果要和Git directory中最新版比较差别,则<commit>=HEAD。如果要和某一个branch比较差别,<commit>=分支名字.
  • git diff <commit> <commit> [<path>...]这个命令用来比较git directory中任意两个<commit>之间的差别,如果想比较任意一个<commit>和最新版的差别,把其中一个<commit>换成HEAD即可。

参考文章:

http://www.worldhello.net/2010/11/30/2166.html

http://guibin.iteye.com/blog/1014369