git学习笔记二

本文将介绍如何使用git命令对本地文件进行一些基本的操作。(下面的一些命令都是windows系统下操作的)

一、创建Git仓库

在使用Git管理我们的项目文件时必须要有一个本地仓库用来记录历史提交。所以首先要创建一个本地仓库。

1.git init

可以使用git init命令创建一个本地仓库。

也可以仓库和项目目录一起创建:

也可以在创建一个名为myProject的裸仓库

我们在创建仓库的时候添加了一个--bare参数,这样就创建了一个裸仓库。何为裸仓库呢?

裸仓库其实就是一个没有工作区的仓库。裸仓库用来做一个存储设备,而不是开发环境。团队开发中使用的中央仓库就应该被创建成一个裸仓库,因为非裸仓库推送分支时有可能会覆盖已有的代码变动。

也就是说在使用Git进行协作开发时,中央仓库应该是裸仓库,开发者的本地仓库是非裸仓库。

注意:一般裸仓库的名称应该是以.git结尾的,像上面的myProject.git。

2、仓库中的目录结构介绍

创建好仓库之后我们进入到项目目录中,会发现有个隐藏的.git目录。关于项目的版本信息都存放在这个目录里。所以如果你想删除某个项目中的git只需要删除这个.git目录即可。

config:这个文件文件夹里是你保存的设置,这里的内容将写入远程URL中,比如你的邮箱地址,用户名等。每一次在控制端使用“git config”修改配置后都会被保存在这里。

description:用来显示对仓库的描述信息

hooks:这里有一个有趣的特性。Git有一套可以自动运行在任何一个有意义的git阶段下的脚本,叫做hooks。hooks可以运行在commit/rebase/pull等状态之前或之后。脚本的名称决定了它什么时候执行。一个有用的pre-push脚本的例子将会被运行以测试控制器(远程控制)中所有样式规则保持一致。

info-exclude:可以将你不想被git处理的文件放到.gitnore文件夹里。

下面我们在之前创建的项目根目录下新建一个index.html文件,然后在当前目录下执行命令:

上面的命令可能你还看不懂,可以先看注释。我们发现.git文件夹的目录结构发生了变化:

可以看到多了一些内容。这些都是git执行commit操作之后自动创建的,git都会将其压缩并存储到自己的数据结构中。这个压缩对象有一个独特的名字,一个哈希码,存储在对象目录下。

简单的说你可以将commit看做是工作目录中的一种快照,但不仅仅是快照。实际上当你commit时,git只做了两件事情来创造你的工作目录的快照:

如果文件没有修改,git仅仅增加压缩文件的名字(hash)到快照中

如果文件被修改过,git就将其压缩,再将压缩后的文件存储到对象的文件夹中然后再添加这个压缩文件的文件名(hash)到快照中去。

这些压缩文件都会被存放在对象文件夹下。比如刚刚我们创建了一个index.html文件提交后,对象目录中多出了三个文件夹,其中第一个是我的index.html压缩文件,另一个对应的是我提交时所创建的快照文件,第三个是commit自身的压缩,因为commit本身也是一个对象,它也会被压缩存储到对象文件夹中。

其实一个commit命令实际上包含4件东西:

工作目录的快照文件的名称(哈希码)

注释

提交者信息

Parent commit的哈希码

其实git中任何事情都可以用一个正确的hash来得到,分支,标签,HEAD它们都是一样的。比如打开refs/hader/master发现:10ea238c57c595e6a2bfa1619e52f2b25321ac6c

发现这和我刚刚commit的哈希吗是完全一样的,这说明head或者master无非是指向commit的一个指针。

好了,这一块讲的可能比较抽象,如果你看懂了最好,看不懂的话也不影响下面的学习。这里仅仅是让我们更加深刻理解git内部是如何工作的。

二、查看文件状态

在git版本控制系统中,文件总共有四种状态(在第一篇中提到过),分别是:

未跟踪状态(Untracked),未修改状态(Unmodified),已经修改状态(Modified),暂存状态(Staged)

可以通过git status命令来查看工作区和暂存区中文件的状态。可以查看哪些更改被缓存了,哪些还没有缓存,以及哪些文件已经被Git追踪了,哪些还没有被Git追踪。git status的输出不会告诉你任何已经提交的项目历史信息。如果你想查看已经提交的文件状态应该使用git log命令(git log命令会在后面详细介绍)。

不能不说git就是强大,我们在查看文件状态时它还会提示你接下来可以对文件进行怎样的操作。

不同的git操作,文件会进入不同的阶段,同时也会改变当前的状态。

如何查看文件的状态我们可以使用git status [filename]。那么我们想查看指定状态的文件都有哪些该怎么办呢?比如我想知道项目中有哪些文件是未跟踪的。

我们可以通过git ls-files命令来查看指定状态的文件列表,如下:

三、提交文件

提交文件就是将工作区中的文件的修改提交到仓库中的一个过程。

1. git add

在提交文件到仓库之前需要先将文件添加到暂存区。git add 这个命令就是将文件添加到暂存区。暂存区会临时缓存我们的修改,用法如下:

2、git commit

将文件加入到暂存区之后就可以提交文件到仓库中了。 提交时git用暂存区中的文件创建一个新的提交,并把此时的节点设为父节点。然后把当前分支指向新的节点。如下图所示,当前分支是master。在运行命令之前,master指向ed489,提交后,master指向新的提交节点f0cec并以ed489作为父节点。

即便当前分支是某次提交的祖父节点,git会同样操作。下图中,在master分支的祖父节点maint分支进行一次提交,生成了1800b。

这样,maint分支就不再是master分支的祖父节点。(此时你可以进行合并分支或者rebase分支)

为父节点。

commit命令具体用法如下:

git commit [filename1 filename2...] -m:提交暂存区中的文件到仓库,其中m表示提交信息。

git commit -a:提交工作区自上次commit之后的变化,直接到仓库区,跳过了add,此命令对新文件无效。

git commit -v:提交时显示所有diff信息(什么是diff信息,后面会详细介绍)

git commit --amend [filename1 filename2...] -m:使用一次新的commit,代替上一次commit,如果暂存区中没有文件,可以重新编辑一下提交信息。因为提交在我们日常开发中会时常发生。很容易就忘了缓存一个文件或者写错了提交信息的格式。--amend标记是修复这些意外的便捷方式。

举个例子:

这是在开发过程中经常会发生的事情。我编辑了一些希望在同一个快照中提交的文件,但是我记了添加其中的一个文件了。想修复这样的错误只需要缓存被忘记的那个文件然后用--amend标记提交就可以了:

加入--no-edit标记会修复提交但是不会修改提交信息。需要修改的话你也可以修改。

我们通过git log来查看一下这两次更改是不是被放在了同一个提交中了。结果如下如所示:

使用git commit --amend命令后提交节点的变化如下图所示:

关于diff

我们知道可以通过git status来查看文件的状态,但是我们只能查看文件处于什么状态,不能了解每个文件的内容具体做了哪些改动。那么我们如何查看文件修改后的差异呢?

可以通过git diff[files]命令可以比较工作区中的文件与暂存区中的文件具体差异。

其中---a表示修改之前的文件,+++b表示修改之后的文件。

git diff --cached:比较暂存区中的文件与之前已经提交的文件

当然你也可以把工作区中的状态和本地仓库中某个历史版本的状态进行diff,命令如下:

git diff HEAD~n,比较本地仓库与工作区中的文件差异

另外有许多方法可以查看两次提交之间的变动,下面是其中的一些例子。

四、撤销提交

有时候我们提交了文件之后,发现有错误,就不想提交了,想撤回。那么该怎样撤回之前的提交呢?撤销提交有三种方式,分别是reset,checkout和revert。下面将分别介绍这三种方法。(其实checkout和revert本质上并没有实现撤销提交,因为它根本没有改变HEAD指针,只是在某些情况下实现了撤销提交的效果)

1、checkout实现撤销提交

git checkout命令有三个作用,检出文件,检出提交,检出分支,这一章先不说检出分支。检出提交会使工作区和某个提交完全匹配。检出文件使你能够查看某个特定文件的旧版本,它用于从历史提交(或者stage缓存)中拷贝文件到工作目录中。而工作目录中剩下的其他文件内容不变。用法如下:

查看某个特定文件之前的版本。它将工作区中的文件变成节点中的那个文件的拷贝,并将它加入到暂存区。

如果没有指定文件,就将指定历史版本中的文件全部检出。

参数可以是提交的哈希码,或者是标签。检出提交是一个只读操作。绝对不会损坏你的仓库(你是不可能对仓库的内容进行修改的)。

如果命令中没有指定提交节点,则会从暂存区中拷贝内容到工作目录中。注意当前分支不会发生变化。

我们可以通过 git log(或者git log --oneline)来找到我们想要看的那个版本的commit ID。

当执行“git checkout HEAD .”或者“git checkout HEAD”命令时,会用HEAD指向的分支中的全部或者部分文件替换暂存区以及工作区中的文件。这个命令是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

在开发的正常阶段,HEAD一般是指向master或者其它分支的,但是当你检出提交的时候,HEAD就不再指向一个分支了,它直接指向这个提交节点。这被称为分离HEAD状态,如下图所示:

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

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

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

2、revert实现撤销提交

git revert也是撤销命令,它和reset的区别在于reset是向前移动指针,git revert是创建一个commit来覆盖要撤销的commit,是继续往后移动指针。

git revert命令用来撤销一个已经提交的快照。它是通过搞清楚如何撤销这个提交引入的更改,然后在最后加上一个撤销更改的新提交,而不是从项目历史中移除这个提交。这避免了Git丢失历史,这一点对于你的版本历史和协作的可靠性来说是很重要的。

举个例子:

git revert的用法:

git revert HEAD:撤销前一次commit

git revert HEAD^:撤销前前一次commit

git revert commitID:(比如:2bf5926)撤销指定的提交。

当我们执行了一次git revert HEAD后会发现在原来的commit后又创建了一个commit并且工作区的内容被上次提交的内容覆盖。

撤销(revert)应该是用在你想要在项目历史中移除整个提交的时候。比如你在追踪一个bug,然后你发现它是一个提交造成的,这个时候revert就很有用了。与其说自己去修复这个bug,然后提交一个快照,不如用git revert撤销这个提交,它帮你做了所有的事情。

3、git reset

reset的字面意思是“重置”,在介绍它之前我们先说一下几个概念。 (其实在前面也已经讲到了)

(1)、三棵树

理解reset和checkout的最简单的方法,就是以git的思维框架(将其作为内容管理器)来管理三颗不同的树。“树”在我们这里的实际意思是“文件的集合”,而不是特定的数据结构。

HEAD:HEAD是指向当前分支的指针,它总是指向该分支的最后一次提交。这表示HEAD将是下一次提交的父节点。通常,理解HEAD的最简单方式,就是将它看做你上一次提交的快照。

Index:索引是你的“预期的下一次提交,暂存区域”,运行git add后,代码就进入“暂存区域”。

Working Directory:可以把工作目录当做“沙盒”。在提交之前对文件进行编辑。

(2)、重置的作用

如果说git revert是一个撤销更改安全的方式,那么git reset就是一个危险的方式。因为使用revert后,如果后悔了还可以再使用一次revert返回去。而使用reset就不能了,一旦reset之后,就不能回到原来的样子。也就是说这个撤销是永远的

如下图所示:

将当前分支重设(reset)到指定的提交节点或者HEAD(默认是HEAD,即最新的一次提交),并且根据参数有可能更新index和working directory(默认参数是mixed)

--hard:重设暂存区和工作区,从commit节点以来在暂存区和工作区中的任何改变都会被丢弃,并把HEAD指向commit节点。

说明:第二次提交的b.txt已经被丢弃!HEAD指针重新指向了第一次提交的节点。彻底回退到某个版本,本地的源码也会变为上一个版本的内容。

--soft:暂存区和工作目录中的内容不做任何改变,仅仅把HEAD指针向某个提交节点。自从

以来的所有改变都会显示在git status的“Changes to be commited”中。

说明:第二次提交的test2被重置到了“changes to be commited”中!HEAD指针重新指向了第一次提交的commitID。回退到某个版本,只回退了commit的信息。如果还要提交,直接commit即可。意思就是暂存区和工作区中的内容没有回退。

--mixed:仅仅重设index,但是不重设working directory。这个模式是默认模式,即当不显示告知git reset模式时,会使用mixed模式。这个模式的效果是,working directory中文件的修改都会被保留,不会丢失,但是也不会被标记成“Changes to be commited”,但是会打出什么还未被更新的报告。

说明:第二次提交的b.txt被重置到了初始状态(上述示例为“Untracked”)HEAD指针重新指向了第一次提交的commitID。回退到某个版本,只保留源码,回退commit和index信息。

当然reset还可以用来解决冲突,这时候就需要使用另外两个模式了merge和keep。关于这一点我们会在分支那一块讲解。

reset不同模式撤销提交的图示:

撤销之后c10b9,da985,ed489这三个提交节点都会被删除。

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

git reset和git revert的区别

git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。

在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别的。因为git revert是用一次逆向的commit“中和”之前的commit,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。

git reset是把HEAD向前移动了一下,而git是HEAD继续往后走,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

4、其他扩展:将文件从暂存区中删除(撤销add操作)

有时候可能发现要提交的文件中有错误,但是已经添加到暂存区了,现在我想把它从暂存区中移除,这种情况该怎么办?git为我们提供了响应的命令。

(1)、git rm --cached filename1 filename2...

这种方法是将指定文件从暂存区中删除,并且不影响工作区的文件。

(2)、git rm相关扩展

git rm filename,不但从暂存区中删除,同是从工作区中也删除

git mv a.txt b.txt,把a.txt改名为b.txt

git clean[options],移除所有未跟踪文件,一般会加上参数-df,-d表示包含目录,-f表示强制清除。

5、git log

git log命令显示已经提交的快照。你可以列出项目历史,筛选,以及搜索特定更改。git status只允许你查看工作目录和暂存区的状态,而git log只作用于提交的项目历史。

log输出可以有很多种自定义的方式,从简单的筛选提交,到用完全自定义的格式显示。

(1)、git log

使用默认格式显示完整的项目历史。如果超出一屏,你可以使用“空格键”来滚动,按“q”键退出。

(2)、git log -n

限制提交的数量。比如git log -n 3,表示只会显示3个提交。

(3)、git log --oneline

将每个提交压缩到一行。当你需要查看项目历史的上层情况时,这会很有用。

(4)、git log --stat

除了git log信息之外,包含了哪些文件被更改了,以及每个文件相对的增删行数。

(5)、git log -p

显示代表每个提交的一堆信息。显示每个提交全部的差异(diff),这也是项目历史中最详细的视图。

(6)、git log --author="

"

搜索特定作者的提交。

可以是字符串或正则表达式。

(7)、git log --grep="

"

搜索提交信息匹配特定的

的提交。

可以是字符串或正则表达式。

(8)、git log...

只显示发生在和之间的提交。两个参数可以是提交ID、分支名、HEAD或者是任何一种引用。

(9)、git log

只显示包含特定文件的提交。查找特定文件的历史这样做会很方便。

(10)、git log --graph --decorate --online

还有一些有用的选项。--graph标记会绘制一幅字符组成的图形,左边是提交,右边是提交信息。--decorate标记会加上提交所在的分支名称和标签。--online标记将提交信息显示在一行,一目了然。

五、创建忽略文件

这个就很简单了,有时候我们不想把某些文件纳入版本控制中,想在提交的时候把一些不想提交的文件跳过。

比如我在使用node开发一个小的应用,但是用到了很多第三方模块,当我提交到远程仓库的时候,如果我把整个项目都提交上去,如果这些第三方模块体积很大的话,再加上网很慢的情况,可能我出去喝杯咖啡回来都还没提交完呢。

所以我们可以把这些模块文件都忽略掉,在package文件中列一个模块清单就可以。

那怎样忽略这些文件呢?

我们需要创建一个名为".gitignore"的文件,这个文件的规则如下:

文中的空行或以井号(#)开始的行将会被忽略。

可以使用Linux通配符。例如:星号*代表任意多个字符,问号(?)代表一个字符,方括号([abc])代表可选字符范围,大括号()代表可选的字符串等。

如果名称的最前面有一个叹号(!),表示例外规则,将不被忽略。

如果名称的最前面是一个路分隔符(/),表示要忽略的文件在此目录下,而子目录中的文件不忽略。

如果名称的最后面是一个路径分隔符(/),表示要忽略的是此目录下该文件名称的子目录,而非文件(默认文件或目录都忽略)。

示例:

更多的规则请看这里:http://www.cnblogs.com/kevingrace/p/5690241.html

小结

本文介绍了文件的不同操作,包括添加到暂存区,提交文件,撤销提交等等,后面的文件我们将介绍git中比较重要的块:git中的分支。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180417G1Q95N00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券