Git基本概念图文详解

来源:互联网 发布:天下第二行书 知乎 编辑:程序博客网 时间:2024/05/17 22:22

基本用法

上面的四条命令在工作目录、暂存目录(也叫做索引)和仓库之间复制文件。

  • git add file(添加单个文件)或者git add.(添加所有改动过的文件)把当前文件放入暂存区域。(注意git add *的区别)

  • git commit file(提交要提交的文件)给暂存区域生成快照并提交。
  • git reset--files(撤销最后一次缓存区域的文件)或者git reset(撤销所有暂存区域文件)

    撤销缓存区域的文件

  • git checkout--files把文件从暂存区域复制到工作目录,用来丢弃本地修改。

你可以用git reset-p或者git checkout-p或者git add-p进入交互模式。也可以跳过暂存区域直接从仓库取出文件或者直接提交代码。

  • git commit-a相当于运行git add把所有当前目录下的文件加入暂存区域再运行。git commit.
  • git commit--files进行一次包含最后一次提交加上工作目录中文件快照的提交。并且文件被添加到暂存区域。
  • git checkout HEAD--files回滚到复制最后一次提交。

约定

后文中以下面的形式使用图片。

绿色的5位字符表示提交的ID,分别指向父节点。分支用橘色显示,分别指向特定的提交。当前分支由附在其上的HEAD标识。这张图片里显示最后5次提交,ed489是最新提交。master分支指向此次提交,另一个maint分支指向祖父提交节点。

命令详解

Diff

有许多种方法查看两次提交之间的变动。下面是一些示例。




git status是用来查看缓存区状态

git diff  是用来查看缓存区与本地之间的差异,gitdiff可以看做查看详细的git status情况

两个命令常常联合使用



Commit

提交时,git用暂存区域的文件创建一个新的提交,并把此时的节点设为父节点。然后把当前分支指向新的提交节点。下图中,当前分支是master。在运行命令之前,master指向ed489,提交后,master指向新的节点f0cec并以ed489作为父节点。

即便当前分支是某次提交的祖父节点,git会同样操作。下图中,在master分支的祖父节点maint分支进行一次提交,生成了1800b。这样,maint分支就不再是master分支的祖父节点。此时,(或者衍合)是必须的。

如果想更改一次提交,使用git commit--amend。git会使用与当前提交相同的父节点进行一次新提交,旧的提交会被取消。

另一个例子是分离HEAD提交,后文讲。

Checkout

checkout命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。当给定某个文件名(或者打开-p选项,或者文件名和-p选项同时打开)时,git会从指定的提交中拷贝文件到暂存区域和工作目录。比如,git checkout HEAD~foo.c会将提交节点HEAD~(即当前提交节点的父节点)中的foo.c复制到工作目录并且加到暂存区域中。(如果命令中没有指定提交节点,则会从暂存区域中拷贝内容。)注意当前分支不会发生变化。

当不指定文件名,而是给出一个(本地)分支时,那么HEAD标识会移动到那个分支(也就是说,我们“切换”到那个分支了),然后暂存区域和工作目录中的内容会和HEAD对应的提交节点一致。新提交节点(下图中的a47c3)中的所有文件都会被复制(到暂存区域和工作目录中);只存在于老的提交节点(ed489)中的文件会被删除;不属于上述两者的文件会被忽略,不受影响。

如果既没有指定文件名,也没有指定分支名,而是一个标签、远程分支、SHA-1值或者是像master~3类似的东西,就得到一个匿名分支,称作detached HEAD(被分离的HEAD标识)。这样可以很方便地在历史版本之间互相切换。比如说你想要编译1.6.6.1版本的git,你可以运行git checkout v1.6.6.1(这是一个标签,而非分支名),编译,安装,然后切换回另一个分支,比如说git checkout master。然而,当提交操作涉及到“分离的HEAD”时,其行为会略有不同,详情见在下面。

HEAD标识处于分离状态时的提交操作

HEAD处于分离状态(不依附于任一分支)时,提交操作可以正常进行,但是不会更新任何已命名的分支。(你可以认为这是在更新一个匿名分支。)

一旦此后你切换到别的分支,比如说master,那么这个提交节点(可能)再也不会被引用到,然后就会被丢弃掉了。注意这个命令之后就不会有东西引用2eecb

但是,如果你想保存这个状态,可以用命令git checkout-bname来创建一个新的分支。

Reset

reset命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。如果不给选项,那么当前分支指向到那个提交。如果用--hard选项,那么工作目录也更新,如果用--soft选项,那么都不变。

如果没有给出提交点的版本号,那么默认用HEAD。这样,分支指向不变,但是索引会回滚到最后一次提交,如果用--hard选项,工作目录也同样。

如果给了文件名(或者-p选项),那么工作效果和带文件名的checkout差不多,除了索引被更新。



Git官网解释:

git reset HEAD     unstage files from index and reset pointer to HEAD

git reset --soft     moves HEAD to specified commit reference, index and staging are untouched

git reset --hard    unstage files AND undo any changes in the working directory since last commit




Merge

merge命令把不同分支合并起来。合并前,索引必须和当前提交相同。如果另一个分支是当前提交的祖父节点,那么合并命令将什么也不做。另一种情况是如果当前提交是另一个分支的祖父节点,就导致fast-forward合并。指向只是简单的移动,并生成一个新的提交。

否则就是一次真正的合并。默认把当前提交(ed489如下所示)和另一个提交(33104)以及他们的共同祖父节点(b325c)进行一次三方合并。结果是先保存当前目录和索引,然后和父节点33104一起做一次新提交。

Cherry Pick

cherry-pick命令"复制"一个提交节点并在当前复制做一次完全一样的新提交。

Rebase

衍合是合并命令的另一种选择。合并把两个父分支合并进行一次提交,提交历史不是线性的。衍合在当前分支上重演另一个分支的历史,提交历史是线性的。本质上,这是线性化的自动的cherry-pick

上面的命令都在topic分支中进行,而不是master分支,在master分支上重演,并且把分支指向新的节点。注意旧提交没有被引用,将被回收。要限制回滚范围,使用--onto选项。下面的命令在master分支上重演当前分支从169a6以来的最近几个提交,即2c33a





补充

删除

git rm file是把文件从缓存区和本地同时删除

git rm--cached是把文件从缓存区删除


git stash
to quickly save some changes that you're not ready to commit or save, but want to come back to while you work on something else.








这个图表现了多种场景,满足了我们在使用Git时耳濡目染的操作情形。

场景1:暂存文件以及取消已暂存的文件

可以参考上图中上面部分黑色箭头标示。当我们通过git init在本地初始化了Git工作目录后,新增了一个README.txt文件时,此时该文件处于Untracked状态。接下来执行命令:

  1. git add README.txt 

add命令可以暂存此文件,此时,状态变更为Staged状态,被放到了Git暂存区中。若我们要提交此文件到Git资源库,就可以执行git commit命令,文件状态变为committed。例如:

  1. git commit -m "first commit" 

有时候,我们希望取消已暂存的文件。例如说,我在工作目录中增加了两个文件,然后暂存了它们。后来发现其中一个文件并不需要在Git中管理,希望能够取消暂存。由于此时的文件处于Staged状态,我们只需要删掉Stage中对此文件的跟踪即可。这时需要执行的命令是:

  1. git rm --cached README.txt 

注意:此时取消暂存的文件从来就不曾提交过,也即是说没有在Git Repository留下过它的身影。这时的取消暂存实则是删掉暂存的信息。与后面场景演示的取消暂存并不相同。(cached表示暂存区的意思)

场景2:修改已提交文件以及取消已暂存的内容

一旦文件被提交,就会在Git Repository形成提交记录(以hash作为键)。倘若我们此时push提交到远程Git服务器,Git服务器应与本地库保持一致。

现在,让我们看看图中红色箭头展现的流程。我们修改了已提交的README.txt文件,于是文件状态就变更为Modified。这部分修改的内容并没有被放入暂存区,若要提交此次修改,就还需要再次执行git add命令,将这次新的修改放入到暂存区。这个流程包括后面的提交都与场景1相似。唯一不同的是“取消已暂存的内容”。

虽然同样是取消暂存,但它与场景1是完全不同的概念。场景1实则是要取消暂存区的文件,因此使用了git rm –cached本质上讲其实是删除。而这里的取消,其实是希望取消暂存区中已经被添加的修改内容,文件本身仍然保留在暂存区中。故而执行的命令为:

  1. git reset HEAD README.txt 

HEAD是何意呢?在Git中,HEAD是一个特别的指针,指向你正在工作的本地分支。当前分支就是master。如下图所示:

而reset命令的意思是重新设置当前的HEAD指针到特定的状态。由于当前的README.txt还没有提交到master分支的Repository中。因此,这条命令实则就是将HEAD指向README.txt文件在当前master分支的Repository状态,从而保证了对README.txt文件而言,暂存区与Repository的一致——取消了README.txt文件在暂存区的内容。

场景3:修改文件以及撤销修改内容

再看图中的绿色箭头与蓝色箭头展现的流程。我们不是初始化git工作目录,而是通过git clone从远程Repository克隆了项目,此时会在当前目录建立git工作目录。此时的文件全部处于Unmodified状态。

现在,我们修改文件,例如hello.java。一旦被修改,文件状态就迁移到Modified状态。倘若需要暂存此次修改,甚至提交到Git Repository,则执行的流程与场景1相同(如蓝色箭头线所示)。

然而,我们可能希望放弃此次修改,即不将修改的内容放入暂存区。这时,应执行checkout命令:

  1. git checkout -- hello.java 

在执行checkout命令时要慎重。因为它要撤销的内容并没有被放入到暂存区或Repository。一旦撤销,就一去不复返了。

概念区分:fetch vs. pull

fetch命令只是将远端数据拉到本地仓库,并不自动合并到当前工作分支。若要合并,还需手动合并。例如,执行git fetch origin,就会抓取自上次克隆以来别人上传到此远程仓库中的所有更新。

pull命令则除了会抓取数据,还能将远端分支自动合并到本地仓库中当前分支。

场景4:撤销提交

在Git中若要撤销提交,可以使用reset或者revert命令。但二者有着显著的区别:

revert命令可以撤销已经提交的快照,但它并不会将该提交从项目的提交历史中移除,而是会判断要撤销的这次提交引入了哪些变化,然后将此变化撤销(此次撤销事实上还是一种变化),再将这次撤销作为一个提交。因此,在执行revert命令后,如果通过git log查看提交历史,可以看到会新增一个revert提交。命令为:

  1. git revert <commit> 

这个commit可以是指定提交对应的hash code。我们也可以用HEAD指针:

  1. git revert HEAD~n 

如果是revert当前提交,则不需要HEAD后的~n。

reset命令就字面意义已经表达了该操作的含义为“重置”。由于Git的提交记录是由HEAD指针指向当前分支。重置就是搬动这个指针到指定的snapshot。如果说revert是一种 安全的撤销方式,则reset就是一种 危险的撤销方式。默认情况下,如果使用reset命令,会将当前的分支回退到指定commit,然后自指定commit到最新commit之间的内容会放在工作目录下,使得我们可以再提交。这个命令为:

  1. git reset <commit> 

与前相同,这个commit就是提交对应的hash code。同样,也可以使用HEAD指针。不过如果是撤销当前提交,与revert不同的是,需要指定为:HEAD~1。这是因为HEAD指针指向了当前提交。reset与revert的意义不一样。revert对应的commit为目标提交,意思为:“撤销目标提交”,因而git revert HEAD,代表的就是“将当前提交撤销”。而reset对应的commit表示将指针移向给定的Commit。如果执行git reset HEAD,代表的就是“将当前指针指向当前提交”,相当于没做任何操作。所以应该执行git reset HEAD~1。

如果确实要撤销操作,而前面的内容并不需要,在使用reset命令时,可以添加–hard参数:

  1. git reset --hard <commit> 

**注意:针对远程的提交记录,应尽量避免使用git reset命令。倘若在本地进行了reset之后,又进行了另外的修改并提交。此时,本地的提交记录与远程的提交记录在reset的那个点产生了分叉。如下图所示: 

此时,如果执行git push,会在本地合并后提交,并同步远程提交记录。则团队其他成员会因为这个变化的提交记录而困惑。由于一部分变更消失,甚至可能导致一些数据被破坏。因此,使用reset命令要格外当心,通常情况,应尽量针对本地提交(未push到远程)进行reset。(对远程分支)优先考虑使用revert命令。








文章主要前半部分摘于:
http://marklodato.github.io/visual-git-guide/index-zh-cn.html#basic-usage

后半部分摘于:
http://os.51cto.com/art/201312/425331.htm

其余部分为自己补充


部分解释参考Git reference
http://gitref.org/basic/


0 0