1. 版本控制系统简介
1.1 何为版本控制
版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了已录下来。每一次文件的改变,文件的版本号都将增加。除了记录版本变更外,版本控制的另一个重要功能是并行开发。软件开发往往是多人协同作业,版本控制可以有效地解决版本的同步以及不同开发者之间的开发通信问题,提高协同开发的效率。并行开发中最常见的不同版本软件的错误(Bug)修正问题也可以通过版本控制中分支与合并的方法有效地解决。
下面举一个简单的例子,大家都用过office word写过文档,在写文档的过程中,可能需要删除原有文档的某一个段落,但又担心该段落之后会再次用到,所以,新建一个文档的副本,然后进行修改。之后,可能会循环重复该流程多次,那么我们的文档库可能就会变成下面的这个样子:
过了一段时间,你可能需要找回某次删除的内容,但是,你已经记不清修改到底在哪个文档中,只能一个一个的查阅。如果文档的版本特别的多的话,相信这种查找的过程会让你抓狂。而且,这些不同版本的文档,不论何时你都是不该删除的,因为,你不知道未来的哪个时间,你可能就会再次用得到。
最原始的代码管理方式与上面的情况类似。
传统文档/代码管理方式的缺点:
- 版本管理混乱;
- 版本回退繁琐;
- 版本合并困难;
- ... ...
版本控制系统就是为了解决类似于上面的问题而发明的,版本控制系统的基本功能就是记录每一次的修改内容、修改时间、修改人、版本、修改说明等等信息,然后通过工具方便的在不同的版本之间进行切换、合并、回退等等操作。
所以,为了能够高效、快捷的管理文档或者代码库,很有必要抛弃原来的“手工版本管理”方式,进入自动化的版本管理时代。
1.2 分布式VS集中式
目前,主流的版本控制系统有两种架构:分布式系统和集中式系统,它们分别是什么?有什么区别呢?
- 集中式版本控制系统:
先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式版本控制系统最大的问题就是必须联网才能工作,遇到带宽不好的情况是,提交、更新较大文件可能会十分的缓慢。而且,集中式版本控制系统容灾性差,万一中心服务器硬盘出现的数据丢失,那后果是很严重的。
- 分布式版本控制系统:
分布式版本控制系统与集中式版本控制系统之间最大的区别就是,分布式版本控制系统不需要“中心服务器”,每一台电脑本地都保留了完整的版本系统,每次代码的提交、回退、分支合并都不需要服务器的参与,本地版本系统就可以搞定。而且,多个不同电脑上的不同版本可以通过网络进行合并、更新。
分布式版本系统的优点显而易见,首先,它可以完全独立的工作,不需要服务器的参与;其次,它具有很高的安全性,某一台电脑的数据丢失后,可以通过其他电脑进行恢复。
现实情况下,分布式版本控制系统也需要一个“中心服务器”,但是,该服务器的作用仅限于为数据的交互提供便利性。
目前,比较流行的集中式版本控制系统包括SVN、CVS等,这两款系统都是开源免费的,时至今日,仍然有很多人在使用。
该文档的主角Git属于分布式版本控制系统,其简单、高效,并且功能完备,是目前最为流行的版本控制系统。
2. Git初体验
2.1 Git诞生记
说起Git的起源,那绝对是一个传奇故事。话说,当年Linus自从1991发明了Linux之后,直到2002年都是通过手工的方式(打补丁的方式)来管理Linux代码,随着Linux代码量的指数增长,Linus有些力不从心,出于开源精神和个人癖好(极度讨厌集中式版本控制系统的性能低下),他选择了一个商业版本的分布式版本控制系统BitKeeper。当然,BitKeeper是免费提供给Linus使用的。
直到2005年,两家都相安无事,但是,一起事件激怒了BitKeeper公司,并收回了BitKeeper对于Linux的免费使用权(具体事件是某位Linux内核开发者试图破解BitKeeper的数据协议)。
但,从不服输的Linus没有向BitKeeper公司低头,而是自己用了两周的时间,用C语言开发了一套分布式版本控制系统,那就是Git。一个月之内,Linux的全部代码已经交由Git处理了,Linus真乃大神也啊!
之后,2008年,随着GitHub网站的兴起,git也随之兴盛起来,发展到现在成为最好用的版本管理工具。
2.2 安装Git
Git支持目前所有主流的操作系统,当然,使用Git之前需要首先安装Git,下面分别介绍一下:
- Linux系统
如果你使用的系统为Ubuntu,那么安装Git其实相当的简单,只需一条命令:
sudo apt-get install git;
- Windows系统
Windows系统的话,可以在Git官网下载合适的版本,就行安装,安装过程十分的简单。
安装完成之后,回到桌面,右键,选择Git Bash Here,正常情况下,会弹出Git的交互终端;
安装完之后,还需要简单的配置,命令如下:
$ git config --global user.name "Your Name" $ git config --global user.email "email@example.com"
上面两条命令主要是为了表明自己的身份信息,这样在代码库中可以清楚的查看每一条提交的相关人员信息。
注:git config命令的--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
2.3 创建Git版本库
版本库,英文名:repository,物理意义上,它其实就是一个目录,我们把需要控制管理的代码、文档放入到该目录下,然后,该目录下的所有文件的添加、删除、修改、回退等信息都可以管理起来。
创建一个Git版本库十分的简单,只需要两步:
- 选择一个合适的地方,创建一个空目录:
注意:如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
- 执行git init创建Git版本库:
注意:学习Git时,尽量不要使用项目代码做实验,否则,操作不当会出现数据丢失。
简单两步,一个Git库就创建好了,下面我们往库里加些文件:
上面命令,创建一个readme.txt文件,并添加了一些内容。
下面说一下,如何将readme.txt文件加入到版本库中,分为两步:
- 使用git add 命令将readme.txt文件加入到Git库中:
$ git add readme.txt
如果有多个文件需要添加到Git库中的话,可以多次调用git add命令。
- 使用git commit命令,将文件提交到仓库中:
$ git commit -m "wrote a readme file" [master (root-commit) eaadf4e] wrote a readme file 1 file changed, 2 insertions(+) create mode 100644 readme.txt
git commit 命令的-m选项,用于表示本次提交的说明信息,说明信息最好统一格式,这样对于后续的Bug管理、版本信息追踪都会大有好处。
3. Git本地管理
3.1 提交修改
之前我们已经添加了readme.txt文档,现在进一步开始修改该文档,并学习一下git status命令的使用方式。
打开readme.txt文档,在第一行添加单词distributed,如下:
现在尝试使用git status,查看仓库的状态:
通过git status可以让我们随时掌握git仓库的状态,上面的输出表示readme.txt已经修改,但是,还未暂存到git仓库。
我们可以通过git diff查看当前的修改内容,输出格式为linux内核补丁所用的格式:
现在,可以提交我们的修改了,所使用的命令还是git add和git commit。首先,使用git add添加readme.txt文件:
$ git add readme.txt
使用git status查看一下版本库的当前状态:
可以准备提交的文件包括:readme.txt,下一步,git commit提交本次的修改。 ![> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFEmWlFM-1616251474903)(media/04789ebe7e7808ae7341d292e0eb7ad6.png)]](img-blog.csdnimg.cn/20210320225…
再次,运行git status命令,可以看到版本库已经没有需要提交的内容了,工作目录是clean的。
如果,git commit完之后,发现提交说明写的有问题,可以通过git commit --amend命令,修改最近一次的提交说明:
- 使用git log查看最近的提交记录:
- 执行git commit --amend命令:
- 修改提交记录为: “add distributed --amend”:
- 再次通过git log查看提交记录:
根据上图可知,最后一次的提交记录已经修改完成。
3.2 版本回退
现在,我们已经学会了如何向版本库中添加文件,并且学会了如何提交修改。下面继续修改readme.txt文件:
Git is a distributed version control system. Git is free software distributed under the GPL.
然后,提交修改:
实际产品开发过程中,我们需要养成一个习惯,那就是代码/文档修改到一定程度时,要将修改的内容提交到版本库。这样,一旦后续代码/文档修改出了问题,或者误删了某些文件,你还可以通过最近的一次commit进行恢复,这个恢复的过程就是版本回退。下面演示一下,如何进行版本回退操作。
- 首先,通过git log查看一下版本库目前几次的提交情况:
可以看到,目前存在三个commit记录。可以通过--pretty=oneline参数,查看简化版的提交记录信息:
注意:需要友情提示的是,你看到的一大串类似1094adb...的是**commit id(版本号)**,和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
- 如果,由于某种原因你想将版本库回退到“add distributed”那次提交版本的话,该如何操作呢?
- 首先,Git必须知道当前版本是哪一个版本,在Git中,用HEAD表示当前版本,HEAD^表示上一个版本,HEAD^^表示上上一个版本,HEAD~100表示往上100版本。好了,那么想回退到上一个版本,执行如下的命令就行了:
$ git reset --hard HEAD^
看一下readme.txt的内容,果然还原了。
- 但是,如果过了一段时间,突然发现需要返回之前的版本,那该怎么操作?其实,只要能找到之前版本的commit id,就可以通过git reset命令就行回退。Git提供了git reflog用于记录每次的命令,那么就可以通过git reflog找到之前版本的commit id了。
红框标记的就是最后一次的版本提交记录,最开始的那串数字就是commit id。好了,执行如下命令返回之前的版本:
$ git reset --hard 3b45c71
通过git log可以看到,版本已经复原了,查看一下文件内容是否复原:
- Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL:
改为指向add distributed:
3.3 工作区和暂存区
在使用Git管理版本时,经常会听到工作区和暂存区这两个概念,理解工作区和暂存区对于理解Git的很多操作十分有帮助,下面分表介绍一下这两个概念。
- 工作区
工作区即我们实际看到的目录,比如learngit就是一个工作区。
- 暂存区
工作区里有一个隐藏目录.git,该目录不是工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
分支和HEAD的概念我们以后再讲。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
下面通过一个简单的练习来更加深入的了解暂存区的概念。
打开readme.txt,增加一行内容:
Git **is** a distributed version control system. Git **is** free software distributed under the GPL. Git has a mutable index called stage.
然后,在工作区,新建一个文件LICENSE,文件内容随便。
首先,用git status查看一下版本库的当前状态:
Git非常清楚地告诉我们,readme.txt被修改了,而LICENSE还从来没有被添加过,所以它的状态是Untracked。
现在,使用两次命令git add,把readme.txt和LICENSE都添加后,用git status再查看一下:
现在,暂存区的状态就变成这样了:
所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。
$ git commit -m "understand how stage works"
现在版本库变成了这样,暂存区就没有任何内容了:
- git diff比较域
由上图可知,git diff后接不同的参数所表示的比较域是不同的:git diff 是只比较比较工作区和暂存区(最后一次add)的区别,git diff --cached是只比较暂存区和版本库的区别,git diff HEAD -- filename 是只比较工作区和版本库(最后一次commit)的区别。三种比较各对应不同命令。
3.4 管理修改
Git之所以比其他版本控制系统性能优秀,是应为它跟踪并管理的是修改,即每次版本之间的差异,而不是简单的文件。
修改可以是新增了一行、删除了一行、增加了一个字符或者新增了一个文件等等,统统都算作修改。
下面通过简单的实例来证明Git是如何管理修改的。
- 打开readme.txt文件,新增一行内容:
$ cat readme.txt Git **is** a distributed version control system. Git **is** free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes.
- 然后,使用git add添加本次修改:
- 然后,再次修改readme.txt文件:
$ cat readme.txt Git **is** a distributed version control system. Git **is** free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files.
提交,然后查看状态:
可以看到第二次修改的内容并未提交。
那就证明了Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
通过git diff HEAD -- readme.txt,可以查看当前最新版本和工作区的区别:
可见,修改并未提交到版本库。再次使用git add、git commit命令经第二次的修改提交到版本库。
3.5 撤销修改
实际的项目开发过程中,不可避免的会犯错,例如,Bug改错了位置、代码注释写错了等等。出现了错误的修改之后,就要及时的撤销修改,下面分别介绍不同场景下的撤销操作。
- 改乱了工作区的某些文件的内容,想直接丢弃这些修改。
打开readme.txt,添加一行内容:
Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. Git add stupid chagnes.
使用git status命令查看一下当前版本库的状态:
你会发现git提示可以通过:git checkout --<file>命令,丢弃工作区修改的内容。那我们执行该命令:
git checkout -- readme.txt
再次运行git status可以看到工作区已经clean了,并且readme.txt已经恢复到了版本库的最新版本。
- 不但改乱了某些文件的内容,而且还将修改提交到了暂存区。
在场景1的基础上,我们使用git add将readme.txt提交到了暂存区,使用git status查看当前版本库的状态:
同样的,Git还是贴心的提示可以使用git reset HEAD <file\>命令,将暂存区的修改转移到未暂存状态(即工作区),我们运行一下该命令,并使用git status查看一下当前状态:
可以看到,readme.txt文件的状态又回到了场景1的状态,那么通过场景1所介绍的方法可以丢弃相应的修改。
- 已经将错误修改提交到了本地的版本库。
如果只是将错误的修改内容提交到了本地的版本库,那完全可以使用3.2节介绍的方法进行版本回退。但,如修改已经提交到远程版本库的话,那谁也无力回天了。