【工具箱】GIT的使用场景

无论学习什么技术,都需要了解该技术的本质。若是靠死记硬背该技术提供的方法或者语法,终归是知其然而不知其所以然,当发现错误时,你根本不知道是什么原因导致的。我在使用Git时,就处于这种知其然而不知其所以然的状态。现在,再来补补课。

Git有三个工作区域,分别为:工作目录(Working Directory)、暂存区(Stage或Index)以及资源库(Repository或Git Directory)。

参考Pro Git一书,它给出了Git的几个要点:

  • 直接快照,而非比较差异:Git与其他版本管理系统的主要差别在于,Git只关心文件数据的整体是否发生了变化,而其他多数版本管理系统则只关心文件内容的具体差异。Git并不保存文件前后变化的差异数据,更像是把变化的文件做一个快照,然后记录在一个微型的文件系统中。每次提交更新时,会比较这个快照。若文件没有变化,Git则只对上次保存的快照作一个链接。你可以理解Git就是一个小型的文件系统。
  • 近乎所有操作都可本地执行:无需多说,这本身就是分布式版本管理系统的特征。
  • 时刻保持数据完整性:保存到Git前,所有数据都要进行内容的校验和(checksum),并将该结果作为数据的唯一标识。Git使用了SHA-1算法计算数据的校验和,并将该结果作为索引,而非文件名。
  • 多数操作仅添加数据

Pro Git一书认为任何一个文件在Git内部可以被分为三种状态:已提交(Committed)、已修改(Modified)和已暂存(Staged)。然而,这并不足以说明一个文件在不同的工作区域所展现的状态。我认为两种状态足以表达Git中的文件,即:未跟踪(Untracked)和已跟踪(Tracked)。而对于已跟踪状态,我又将其分为:未修改的(Unmodified),Modified(已修改的),暂存的(Staged)和已提交的(committed)。下图基本表达了我的思路:

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

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

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

git add README.txt

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

git commit -m "first commit"

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

git rm --cached README.txt

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

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

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

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

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

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命令:

git checkout -- hello.java

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

概念区分:fetch .vs. pull

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

场景4:撤销提交

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

git revert <commit>

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

git revert HEAD~n

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

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

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与revert的区别:

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

git reset --hard <commit>

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

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

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2014-08-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程微刊

git把本地文件上传到github上的步骤

875
来自专栏wannshan(javaer,RPC)

git 项目分支管理

2010年人家写的,(2010年我还不认识git)。原文在这http://nvie.com/posts/a-successful-git-branching-m...

27210
来自专栏散尽浮华

Git日常操作命令梳理

在日常的Git版本库管理工作中用到了很多操作命令,以下做一梳理: 查看分支列表,带有*的分支表示是当前所在分支 [root@115~~]#git branch ...

1827
来自专栏用户画像

Git冲突:commit your changes or stash them before you can merge.

git stash: 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中。 ...

552
来自专栏青玉伏案

Git知识总览(六) Git分支中的远程操作实践

前几篇博客陆陆续续的讲了好多关于Git操作的内容, 其中在上篇博客聊了《Git中的merge、rebase、cherry-pick以及交互式rebase》,本篇...

32311
来自专栏orientlu

Git 使用记录 - 各种撤销

以上操作用于撤销保存在工作区修改,但是不会撤销暂存区中的修改。 由于修改还没记录到 git 中,撤销无法恢复,请慎重!

733
来自专栏企鹅号快讯

GIT 检查、撤销修改简明教程

有趣有内涵的文章第一时间送达! ? 说明:本教程的所有操作都在master分支上,且仅用于个人代码仓库管理,操作的实用性有待研究。 4个区 ? 5种状态 未修改...

1698
来自专栏青玉伏案

Git知识总览(三) 分支的创建、删除、切换、合并以及冲突解决

前两篇博客集中的聊了git的一些常用命令,具体请参见《Git知识总览(一) 从 git clone 和 git status 谈起》、《Git知识总览(二) g...

3349
来自专栏用户2442861的专栏

cmake教程4(find_package使用)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

723
来自专栏Golang语言社区

厚土Go学习笔记 | 22. 实现Pic

package main import ( "fmt" "image" "bytes" "image/png" "en...

3468

扫描关注云+社区