循序渐进的 Git 入门教程
作者: dkvirus 发表于: 2017-07-04 22:35:00 最近更新: 2018-07-06 09:00:37

一、Git 由来

1. 为什么需要版本控制

举一个简单的例子,如果你用Microsoft Word写过长篇大论,那你一定有这样的经历:

想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的Word文档变成了这样:

为什么需要版本控制

过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。

看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。

更要命的是,有些部分需要你的其他同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。

于是你想,如果有一个软件,不但能自动帮我记录每次文件的改动,还可以让同事协作编辑,这样就不用自己管理一堆类似的文件了,也不需要把文件传来传去。如果想查看某次改动,只需要在软件里瞄一眼就可以,岂不是很方便?

这个软件用起来就应该像这个样子,能记录每次文件的改动:

版本 用户 说明 日期
1 张三 删除了第一段简介 2015/4/18 13:25
2 张三 增加了第二,三,四,五段的内容 2015/4/19 12:30
3 李四 修改了第三段合同金额数据 2015/4/20 09:09
4 王五 修改了第四段关于后续维护的条款 2015/5/01 11:22

对每一次修改进行记录,每一次修改都是一个新的版本,这就是版本控制。

2. 集中式与分布式版本控制区别

集中式版本控制 版本仓库 在中央服务器上,每次干活都需要从该服务器取得最新代码,干完活再将代码推送到服务器上,集中式版本控制系统最大的毛病就是必须联网才能工作。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。

集中式版本控制图解

分布式版本控制 版本仓库 就在个人计算机上,因此不需要联网也能做版本管理,而且速度奇快无比。

既然分布式的版本仓库都存在个人计算机上,那多人协作如何实现呢?要在本地创建版本仓库,需要注册一个用户名和邮箱,别人只要知道你的用户名和邮箱,就可以从你版本库获取代码或者提交代码了。

分布式版本控制图解

3. 牛是怎么定义的

很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。

Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?

事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!

你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。

不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。

安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。

Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:

Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。

Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。

历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。

二、Git 安装

2.1 下载安装包

Git 是由 Linus 花了一个月时间创造的,最初只在 Linux 系统上运行。由于 window 的用户很多,有大牛写了 window 版本的 Git,叫做:msysgit。(不用担心与 Git 会不会不一样,答案是一样的)

Git 安装程序百度网盘 下载完成后下一步下一步安装即可。

2.2 校验是否安装成功

  • 安装成功后,在 [开始] -> [所有程序] 中可以看到 [Git Bash],这是 Git 自带的命令行工具。
  • 当然我们也可以使用 window 系统自带的命令行工具 [Dos],打开 Dos 窗口后输入 git 回车会看到相关信息。

三、版本仓库(简称版本库)

1. 什么是版本库

前面提到 Git 可以创建本地 版本仓库,用于管理本地文件的不同版本。看不见摸不着的东西总让人发虚,让我们实践操作一下为 git-demo 目录创建一个本地版本仓库吧~

1
2
3
$ mkdir git-demo        // 创建目录 git-demo
$ cd git-demo // 切换到 git-demo 目录下
$ git init // 创建版本仓库

可以看到 git-demo 目录下出现了一个 .git 目录,git-demo 目录(除了 .git 目录以外)叫做 工作空间,.git 目录就是上面一直说的 版本仓库,用来管理工作空间的每一个版本信息。

什么是版本库

如果没有看见 .git 目录,是由于你的计算机默认将这个文件夹隐藏起来了,通过以下操作可以显示出来看到:

显示隐藏的文件夹

最佳实践是每一次进入工作空间,不管进行多长时间的工作,修改或是删除了哪些文件,在你准备休息的时候都应该将当前工作空间放到版本库中做一次存储,也就是 创建一个当前状态的版本

这个过程就好比玩网络游戏,在进入 Boss 之前对游戏设置断点存储,这样如果 Boss 打失败了,可以选择断点存储回到进入 Boss 前的那个状态。

这里的断点存储就相当于将工作空间放到版本库中进行一次断点存储。如果之后对工作空间的修改并不满意,可以从版本库中找到之前存储的那个版本,替换当前工作空间即可回到上一个版本。

注意:该文件夹看看就行,里面的东西一个也别动,否则你的版本库有可能会崩溃。

2. 版本库的设计原理

2.1 将文件添加到版本库

1)在 git-demo 目录下新建文件 readme.txt 并写入如下内容。

1
create a readme.txt file.

2)输入 git status 查看 工作空间 的状态

1
$ git status

状态值:Untracked files,表示这是一个新增的文件

status1

3)将 readme.txt 添加到版本库

1
$ git add readme.txt

4)输入 git status 查看状态

状态值:Changes to be commited,表示版本库已经知道了工作空间被修改了

status2

5)将 readme.txt 提交到版本库

1
$ git commit -m "add a readme.txt file"

6)输入 git status 查看状态

状态值:nothing to commit,表示工作空间的修改已经作为一个版本放到了版本库

status3

7)修改 readme.txt 文件,内容如下:(d:\git-demo\readme.txt)

1
2
create a readme.txt file.
This is the second revision of the document.

8)输入 git status 查看状态

状态值:Changes not staged for commit,表示版本库知道文件被修改了,只是还没有提交

status4

2.2 git 设计原理

  • 首先通过 git add 指令将工作区中的内容添加到暂存区(stage);
  • 再通过 git commit 指令将暂存区中内容添加到版本库中。

版本库设计原理图

看到上图我们不禁会想:为什么 git 的作者在设计时不直接将工作区中修改的文件直接添加到master版本库中,而要在中间再加一层 stage 暂存区呢?

dk 想了又想,再次做出推测,抽象成思维导图就像下面这样:

git 是如何设计的

寄快递对比图

对比上面两张思维导图,可得出如下结论:

  • 如果小明寄快递到代收点,代收点马上就将快递送到快递公司;小红紧接着又将快递寄到代收点,代收点再次送到快递工资;与代收点在一天内收到小明、小红、小花的快递,在晚上的时候一并将快递送到快递公司。哪种看起来更好不言而喻。
  • 版本库,作为一个仓库保存着不同版本的信息,如果每一次小修改都打一条版本信息放到版本库,那无疑会大大增加版本库的容量。最佳实践时将一定时间内的所有修改作为一条版本放到版本库中最节约资源。
  • 如果小明寄快递到代收点,后来发现东西记错了,想要取回来,直接去代收点拿回来就可以了。如果没有代收点,小明的快递直接寄到快递公司,被快递公司放到了大货车上就不是那么容易拿回来了。放到版本库中的暂存区也就是方便你撤销你的修改。

2.3 暂存区(stage)

暂存区管理的是修改,不是文件。

老的版本控制系统比如 svn,管理的是文件,也就是你每一次将本地代码提交到仓库时,实际上提交的都是文件,而文件如果一多,就会导致同步速度很慢。(使用过 svn 的应该有感受)

Git 的作者考虑到这一点,舍弃了管理文件的思想,使用 管理修改 的新新思想,这也就是为什么使用 Git 速度很快的原因。下面示例说明:

1)在 git-demo 目录下新建 test.txt 文件,并写入如下内容.

1
this is a test.txt file.

2)将 test.txt 文件添加到暂存区

1
$ git add test.txt

3)修改 test.txt 文件

1
2
this is a test.txt file.
This is the second revision of the document.

4)将 test.txt 文件提交到分支仓库

1
$ git commit -m "add test.txt"

5)使用 git status 查看状态

1
$ git status

管理的是修改

明明 commit 了 test.txt 文件,将 test.txt 这个文件提交给版本仓库了,为什么还显示有东西要提交呢?

我们来分析一下这个过程:

管理的是修改

  • 在示例代码中,第一次新增 test.txt 文件后使用 git add 把它 放到了暂存区 中;
  • 第二次修改 test.txt 文件后并没有添加暂存区而直接使用 git commit 操作将暂存区中内容提交到分支仓库中;
  • 使用 git status 看到仍有一个修改,提示:”Changes not staged for commit”(有个修改没有放到暂存区中);
  • 如果管理的是文件,在使用 git commit 时应该就将 test.txt 文件直接添加到版本库中。使用管理修改而不管理文件的目的是让这个过程变得更快,因为修改可以是一段描述性的字符串即可。

使用 git status 查看工作空间有几种状态,看到英文提示就应该知道当前处于何种状态,总结如下:

文件在版本库中的位置

2.4 Git 我们主要学些什么

经过上面的学习,我们知道使用版本控制就是将本地工作区的代码在分支仓库中进行备份,而我们关心的操作就是如何 从本地到暂存区再到分支仓库 这一条流水线的正常工作。

git 要学什么

已经学习过在这条流水线上 新增操作,接下来学习 撤销操作删除操作

当然,上述三种操作可以看成是将 文件塞到分支仓库,如何从分支仓库中将备份取出来,还要学习回滚版本库中指定版本。

3. 回滚指定版本

3.1 修改工作空间

1)修改 d:\git-demo 目录下的 readme.txt

1
2
3
create a readme.txt file.
This is the second revision of the document.
today is a sunny day.

2)使用 git add 指令添加到暂存区中

1
$ git add readme.txt

3)使用 git commit 指令将这一条版本信息添加到分支仓库中,别忘了写版本描述信息,也就是 -m 后面紧跟着的字符串。

1
$ git commit -m "today is a sunny day"

3.2 查看版本日志信息

1)前面说过分支仓库是存放版本信息的,可以使用 git log 指令查看某个分支仓库中的版本信息。

1
$ git log

git log

可以看到 当前分支仓库中有两个版本。上图中每条版本信息会包含四项内容:

  • commit:每创建一条版本信息时,都会生成一个很大的数字,并用十六进制表示,目的是根据这个 commit id 可以找到对应的版本信息;
  • Author:提交这条版本信息的人是谁,记录每个版本是谁提交的,出了问题好找对应的负责人解决问题;
  • Date:记录提交到分支仓库的时间;
  • 最下面用红框框起来的东东叫做版本描述信息,如果没有它,你看到的只是一大串数字,压根不知道这条版本到底做了哪些修改。

2)感觉日志信息输出太多,可以使用 git log --pretty=oneline 在一行显示日志。

1
$ git log --pretty=oneline

git log pretty=oneline

3.3 回滚到指定版本

上面查看了分支仓库中有两个版本,现在在体验下版本控制工具的神奇之处,将工作空间回滚到第一个版本,也就是 “add a readme.txt file” 的那个版本。

1)在回滚之前,先查看下当前 readme.txt 的内容:

1
2
3
create a readme.txt file.
This is the second revision of the document.
today is a sunny day.

2)使用 git reset --hard <commit id> 指令进行版本回滚

1
$ git reset --hard b4977875

版本回滚

说明:–hard 后面跟着的字符串是要回滚到的那条版本信息对应的 commit id,当然不需要全部写,写前面 7-8 位即可。

3)此时再打开 readme.txt 文件,会看到内容如下:

1
create a readme.txt file.

4)但是,当我们再次查看历史记录时会发现分支仓库中只有一条版本信息了:

回滚版本后查询日志

这就很不爽了,刚才只是测试版本库的回滚功能,并不想丢弃 “today is a sunny day” 那条版本信息。难道回不去?答案当然是不!

使用 git reflog 指令查看刚才所有操作日志:

查看刚才所有操作日志

5)重复 git reset –hard 就可以回到 “today ….” 的那个版本了。这里就不再赘述,自行练习即可。

3.4 git log 与 git reflog 的区别

通过上面的操作,我们看到 git loggit relog 好像都可以查询日志记录,那它们之间又什么区别呢?

  • git log:只查询文件 修改/新增/删除 等与文件有关的日志记录;
  • git reflog:不仅记录与文件有关的记录,还会记录对分支仓库的回滚记录

log 与 reflog 的区别

4. 撤销操作

4.1 撤销工作空间修改内容

工作空间有内容修改,但是还未提交到暂存区。

在写代码的时候,如果没有提前将整个逻辑理清楚就开始修改之前的代码,那么大多数情况到最后自己都不知道写的啥,这时候就会想要回到自己修改之前的那个版本。

工作区撤销

1
$ git checkout -- <filename>

git checkout -- <filename> 用于从版本仓库中取出指定文件/目录。

4.2 提交到暂存区,如何撤回?

  • 对工作区内容 readme.txt 进行修改 —— 状态1;
  • 将修改后的文件 readme.txt 添加到暂存区,此时没有提交到版本仓库 —— 状态2;
  • 再次对工作区内容 readme.txt 进行修改 —— 状态3。

撤回场景2

现在想要将状态3撤回到原始状态,我们分析下如何做:

1)状态3 撤回到状态2

1
2
// $ git checkout -- <filename>
$ git checkout -- readme.txt

2)状态2 撤回到状态1

1
2
// $ git reset HEAD <filename>
$ git reset HEAD readme.txt

3)状态1 撤回到 原始状态

1
2
// $ git checkout -- <filename>
$ git checkout -- readme.txt

4.3 提交到版本仓库,如何撤回?

这种情形最简单,提交到版本仓库,参考 3.3 回滚到上一个版本即可。

1)查看分支仓库版本备份信息

1
$ git log --pretty=oneline

撤回提交1

2)使用 git reset 指令回到上一个版本

1
$ git reset --hard 7e46747

撤回提交2

四、分支

1. 分支简介

1.1 分支与版本库的关系

当一个版本仓库被创建成功时,它会默认的为我们创建一个 master 分支仓库,如下图所示:

版本库设计原理图

我们姑且只看右边:

1)一个版本库中可以包含很多个分支仓库

前面由于只有一个 master 分支仓库,所以我们好像把 master 的分支仓库就看成了版本库,其实它是版本库的一部分。

版本库与分支的关系

2)一个分支仓库中存放着不同时间的版本备份文件

每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。

分支仓库是干嘛的

1.2 为什么需要多个分支

分支提高效率

多人协同工作时,每个人可以从主分支上创建一个子分支,在子分支上开发,开发完成后,再往主分支上进行合并。

2. 操作分支

2.1 从主分支上创建 dev 分支

1
$ git branch dev        // 创建分支 dev

2.2 查看版本库中有哪些分支

当前分支前面会标一个 * 号。

1
2
3
$ git branch
dev
* master

2.3 切换到 dev 分支

1
$ git checkout dev

也可以使用更简单的方法 一步创建并切换分支

1
$ git checkout -b dev   // 创建并切换分支

相当于下面两行代码:

1
2
$ git branch dev        // 创建分支 dev
$ git checkout dev // 切换分支

2.4 合并 dev 分支

1)测试 dev 分支上修改的文件在合并前对主分支是否有影响

修改 readme.txt 文件,内容如下:

1
2
3
4
5
create a readme.txt file.
This is the second revision of the document.
today is a sunny day.

this is dev branch. // 新增加的一句话

2)添加并提交到 dev 分支仓库

1
2
$ git add readme.txt 
$ git commit -m "update readme.txt"

3)切换到主分支查看 readme.txt,可以看到没有刚才新增的那句话。

1
$ git branch master

4)合并 dev 分支

将 dev 分支合并到 master 主分支,需要先切换到 master 分支,在进行合并操作。

1
2
$ git checkout master
$ git merge dev

5)再次查看 readme.txt

可以看到刚才在 dev 分支上对 readme.txt 文件的修改也一并合并过来了。

2.5 删除 dev 分支

将 dev 分支合并到 master 分支上之后, dev 分支也就没有用了,可以进行删除。

1)切换到 master 分支并进行删除操作

1
2
$ git branch master
$ git branch -d dev

2)查看版本库中分支,发现 dev 分支已被删除,只剩 master 分支了。

1
2
$ git branch 
* master

五、标签

1 标签简介

我们知道,每一次 commit 操作都会往当前分支提交一个版本备份,这个版本备份包含以下内容:

  • commit:每创建一条版本信息时,都会生成一个很大的数字,并用十六进制表示,目的是根据这个 commit id 可以找到对应的版本信息;
  • Author:提交这条版本信息的人是谁,记录每个版本是谁提交的,出了问题好找对应的负责人解决问题;
  • Date:记录提交到分支仓库的时间;
  • 最下面用红框框起来的东东叫做版本描述信息,如果没有它,你看到的只是一大串数字,压根不知道这条版本到底做了哪些修改。

如果我们想要找某个版本,可以说 那个谁,把 master 分支上 commit id 为 6a5819e 的那个版本打下包”。

commit id 一般冗长而又没有规律可寻,用 commit id 去快速找某个版本不是明智之举。标签就是解决上面那种尴尬的情况

一个标签与一个 commit id 号一一对应,比如我们给 commit id 为 6a5819e 的那个版本备份打上个标签叫做 v1.0,之后只需说 那个谁,把 master 分支上 v1.0 那个版本打个包”。

2 操作标签

2.1 给当前分支版本打标签

1)切换到 master 分支

1
$ git checkout master

2)查看分支仓库中版本备份日志

最近的版本备份信息

3)使用 git tag 指令为任意版本备份打标签

1
2
// $ git tag <tagname> <commit id>
$ git tag v1.0 12964b

:commit id 不需要全部写上,截取前面 6-7 位即可。

4)如果不加上 ,也是可以的,默认给最近的版本信息打标签

1
$ git tag v1.0

5)有时我们打的标签可能不能很好的表明意思,可以像 git commit 一样使用 -m 参数来记录描述信息,-a 参数指定标签名。

1
$ git tag -a v1.0 -m "update readme.txt" 12964b

2.2 查看当前分支有哪些标签

上面新增了几个标签,我们想查看下当前分支下有哪些标签,可以使用指令 git tag:

1
2
3
$ git tag
v0.9
v1.0

2.3 标签打错了,重新打

上面的 v1.0 版本打错了,应该打成 v1.0 标签的同时带上描述信息。

1)删除 v1.0 标签

1
2
// $ git tag -d <tagname>
$ git tag -d v1.0

2)重新打上 v1.0 标签,同时使用 -m 参数带上描述信息

1
$ git tag -a v1.0 -m "update readme.txt" 12964b

六、报错

1. 报错【user-name-has-multiple-value】?

安装完 git 之后,需要在命令行中输入名称和邮箱来登录自己的 git 。

1.1 网上教程是这么教的

1
2
$ git config --global user.name "你的 git 的名称"
$ git config --global user.email "你的 git 的邮箱"

然而现实并没有想象中顺利,报了下面的错误:git

1.2 原因

1
$ git config --list        // 这条指令可以查看到 git 相关配置信息,可以看到已经无意间添加了多个 name 值

查看 git 配置信息

1.3 解决方法

1
2
$ git config --global --replace-all user.name "你的 git 的名称"
$ git config --global --replace-all uesr.email "你的 git 的邮箱"

做完这一步,再键入 $ git config –list 会发现 name 和 email 只有一个值了,这时候就不会报错了。

首页
友链
归档
dkvirus
动态
RSS