zoukankan      html  css  js  c++  java
  • Git索引

    我想如果看过《Git历险记》的前面三篇文章的朋友可能已经知道怎么用git addgit commit这两个命令了;知道它们一个是把文件暂存到索引中为下一次提交做准备,一个创建新的提交(commit)。但是它们台前幕后的一些有趣的细节大家不一定知晓,请允许我一一道来。

    Git 索引是一个在你的工作目录(working tree)和项目仓库间的暂存区域(staging area)。有了它, 你可以把许多内容的修改一起提交(commit)。 如果你创建了一个提交(commit),那么提交的一般是暂存区里的内容, 而不是工作目录中的内容。

    一个Git项目中文件的状态大概分成下面的两大类,而第二大类又分为三小类:

    1. 未被跟踪的文件(untracked file)

    2. 已被跟踪的文件(tracked file)        

      1. 被修改但未被暂存的文件(changed but not updated或modified)

      2. 已暂存可以被提交的文件(changes to be committed 或staged)

      3. 自上次提交以来,未修改的文件(clean 或 unmodified)

    看到上面的这么多的规则,大家早就头大了吧。老办法,我们建一个Git测试项目来试验一下:

    我们先来建一个空的项目:

    $rm -rf stage_proj
    $mkdir stage_proj
    $cd stage_proj
    $git init
    Initialized empty Git repository in /home/test/work/test_stage_proj/.git/

    我们还创建一个内容是“hello, world”的文件:

    $echo "hello,world" > readme.txt

    现在来看一下当前工作目录的状态,大家可以看到“readme.txt”处于未被跟踪的状态(untracked file):

    $git status
    # On branch master
    #
    # Initial commit
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #   readme.txt
    nothing added to commit but untracked files present (use "git add" to track)

    把“readme.txt"加到暂存区: $git add readme.txt

    现在再看一下当前工作目录的状态:

    $git status
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    #   new file:   readme.txt
    #

    可以看到现在"readme.txt"的状态变成了已暂存可以被提交(changes to be committed),这意味着我们下一步可以直接执行“git commit“把这个文件提交到本地的仓库里去了。

    暂存区(staging area)一般存放在“git目录“下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。索引是一个二进制格 式的文件,里面存放了与当前暂存内容相关的信息,包括暂存的文件名、文件内容的SHA1哈希串值和文件访问权限,整个索引文件的内容以暂存的文件名进行排 序保存的。

    但是我不想马上就把文件提交,我想看一下暂存区(staging area)里的内容,我们执行git ls-files命令看一下:

    $git ls-files --stage
    100644 2d832d9044c698081e59c322d5a2a459da546469 0   readme.txt

    我们如果有看过上一篇文章里 的"庖丁解牛", 你会发现“git目录“里多出了”.git/objects/2d/832d9044c698081e59c322d5a2a459da546469”这 么一个文件,再执行“git cat-file -p 2d832d” 的话,就可以看到里面的内容正是“hello,world"。Git在把一个文件添加暂存区时,不但把它在索引文件(.git/index)里挂了号,而 且把它的内容先保存到了“git目录“里面去了。

    如果我们执行”git add“命令时不小心把不需要的文件也加入到暂存区中话,可以执行“git rm --cached filename" 来把误添加的文件从暂存区中移除。

    现在我们先在"readme.txt"文件上做一些修改后:

    $echo "hello,world2" >> readme.txt

    再来看一下暂存区的变化:

    $git status
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    #   new file:   readme.txt
    #
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #   modified:   readme.txt
    #

    大家可以看到命令输出里多了一块内容:“changed but not updated ...... modified: readme.txt”。大家可能会觉得很奇怪,我前面不是把"readme.txt"这个文件给添加到暂存区里去了吗,这里怎么又提示我未添加到暂存区 (changed but not updated)呢,是不是Git搞错了呀。

    Git 没有错,每次执行“git add”添加文件到暂存区时,它都会把文件内容进行SHA1哈希运算,在索引文件中新加一项,再把文件内容存放到本地的“git目录"里。如果在上次执行 “git add”之后再对文件的内容进行了修改,那么在执行“git status”命令时,Git会对文件内容进行SHA1哈希运算就会发现文件又被修改了,这时“readme.txt“就同时呈现了两个状态:被修改但未 被暂存的文件(changed but not updated),已暂存可以被提交的文件(changes to be committed)。如果我们这时提交的话,就是只会提交第一次“git add"所以暂存的文件内容。

    我现在对于“hello,world2"的这个修改不是很满意,想要撤消这个修改,可以执行git checkout这个命令:

    $git checkout -- readme.txt

    现在再来看一下仓库里工作目录的状态:

    $git status
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    #   new file:   readme.txt
    #

    好的,现在项目恢复到我想要的状态了,下面我就用git commit 命令把这个修改提交了吧:

    $git commit -m "project init"
    [master (root-commit) 6cdae57] project init   1 files changed, 1 insertions(+), 0 deletions(-)    create mode 100644 readme.txt

    现在我们再来看一下工作目录的状态:

    $git status
    # On branch master
    nothing to commit (working directory clean)

    大家可以看到“nothing to commit (working directory clean)”;如果一个工作树(working tree)中所有的修改都已提交到了当前分支里(current head),那么就说它是干净的(clean),反之它就是脏的(dirty)。

    SHA1值内容寻址

    正如Git is the next Unix 一文中所说的一样,Git是一种全新的使用数据的方式(Git is a totally new way to operate on data)。Git把它所管理的所有对象(blob,tree,commit,tag……),全部根据它们的内容生成SHA1哈希串值作为对象名;根据目 前的数学知识,如果两块数据的SHA1哈希串值相等,那么我们就可以认为这两块数据是相同 的。这样会带来的几个好处:

    1. Git只要比较对象名,就可以很快的判断两个对象的内容是否相同。

    2. 因为在每个仓库(repository)的“对象名”的计算方法都完全一样,如果同样的内容存在两个不同的仓库中,就会存在相同的“对象名”。

    3. Git还可以通过检查对象内容的SHA1的哈希值和“对象名”是否匹配,来判断对象内容是否正确。

    我们通过下面的例子,来验证上面所说的是否属实。现在创建一个和“readme.txt“内容完全相同的文件”readme2.txt“,然后再把它提交到本地仓库中:

    $echo "hello,world" > readme2.txt
    $git add readme2.txt
    $git commit -m "add new file: readme2.txt"
    [master 6200c2c] add new file: readme2.txt
    1 files changed, 1 insertions(+), 0 deletions(-)
    create mode 100644 readme2.txt

    下面的这条很复杂的命令是查看当前的提交(HEAD)所包含的blob对象:

    $git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p
    100644 blob 2d832d9044c698081e59c322d5a2a459da546469    readme.txt
    100644 blob 2d832d9044c698081e59c322d5a2a459da546469    readme2.txt

    我们再来看看上一次提交(HEAD^)所包含的blob对象:

    $git cat-file -p HEAD^ | head -n 1 | cut -b6-15 | xargs git cat-file -p
    100644 blob 2d832d9044c698081e59c322d5a2a459da546469    readme.txt

    很明显大家看到尽管当前的提交比前一次多了一个文件,但是它们之间却是在共用同一个blob对象:“2d832d9”。

    No delta, just snapshot

    Git 与大部分你熟悉的版本控制系统,如Subversion、CVS、Perforce 之间的差别是很大的。传统系统使用的是: “增量文件系统” (Delta Storage systems),它们存储是每次提交之间的差异。而Git正好与之相反,它是保存的是每次提交的完整内容(snapshot);它会在提交前根据要提交 的内容求SHA1哈希串值作为对象名,看仓库内是否有相同的对象,如果没有就将在“.git/objects"目录创建对应的对象,如果有就会重用已有的 对象,以节约空间。

    下面我们来试验一下Git是否真的是以“snapshot”方式保存提交的内容。

    先修改一下"readme.txt",给里面加点内容,再把它暂存,最后提交到本地仓库中:

    $echo "hello,world2" >> readme.txt
    $git add readme.txt
    $git commit -m "add new content for readme.txt"
    [master c26c2e7] add new content for readme.txt   1 files changed, 1 insertions(+), 0 deletions(-)

    我们现在看看当前版本所包含的blob对象有哪些:

    $git cat-file -p HEAD | head -n 1 | cut -b6-15 | xargs git cat-file -p
    100644 blob 2e4e85a61968db0c9ac294f76de70575a62822e1    readme.txt
    100644 blob 2d832d9044c698081e59c322d5a2a459da546469    readme2.txt

    从上面的命令输出,我们可以看到"readme.txt"已经对应了一个新的blob对象:“2e4e85a”,而之前版本的"readme.txt“对应的blob对象是:“2d832d9”。下面我们再来看一看这两个”blob“里面的内容和我们的预期是否相同:

    $git cat-file -p 2e4e85a
    hello,world
    hello,world2
    $git cat-file -p 2d832d9
    hello,world

    大家可以看到,每一次提交的文件内容还是全部保存的(snapshot)。

    小结

    Git内在机制和其它传统的版本控制系统(VCS)间存在本质的差异,所以Git的里"add"操作的含义和其它VCS存在差别也不足为奇,“git add“不但能把未跟踪的文件(untracked file)添加到版本控制之下,也可以把修改了的文章暂存到索引中。

    同时,由于采用“SHA1哈希串值内容寻值“和”快照存储(snapshot)“,让Git成为一个速度非常非常快的版本控制系统(VCS)。

  • 相关阅读:
    Python 学习日记 第七天
    Python 学习日记 第六天
    Python 学习日记 第五天
    Python 学习日记 第四天
    Redis 中的数据类型及基本操作
    Asp.net mvc 中View 的呈现(二)
    Asp.net mvc 中View的呈现(一)
    Asp.net mvc 中Action 方法的执行(三)
    Asp.net mvc 中Action 方法的执行(二)
    Asp.net mvc 中Action 方法的执行(一)
  • 原文地址:https://www.cnblogs.com/wzyxidian/p/5518961.html
Copyright © 2011-2022 走看看