【工具篇】版本控制软件:Git
Git
::: info
Github的master分支调整为main了
:::
版本控制
什么是版本控制
版本控制是一种在开发过程中用于管理我们对文件、目录等工程内容修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术
你可以把一个版本控制系统(缩写VCS)理解为一个“数据库”,在需要的时候,它可以帮你完整地保存一个项目的快照。当你需要查看一个之前的快照(称之为“版本”)时,版本控制系统可以显示出当前版本与上一个版本之间的所有改动的细节。
版本控制系统会记录所有对项目文件的更改。这就是版本控制,听起来很简单。
最简单的版本控制就是保存项目内容的一个备份,编号为”A”,然后基于原始项目进行修改,修改后保存为版本”B”,保留软件不同状态的数份copy,并且适当编号。许多大型开发案都是使用这种简单技巧。虽然这种方法能用,但是很没效率。一是因为保存的数份copy几乎完全一样,也因为这种方法要高度依靠开发者的自我纪律,从而导致错误。
为什么使用版本控制
版本存储
常见的版本存储方式就是在本地多次备份不同版本的文件,即使按照通用的命名格式保存后,还需要花费大量的时间来分析整理这些备份文件,而且这种操作很容易出错,而且经常性的不知道为什么保存,保存了什么变动的内容,因为我们很少花更多的时间去记录和观察每一个重要的变化
版本控制系统就完美的解决了我们的问题,每当你提交一次对项目新的改动时,他会基于最原始的文件,保存每一个细节的变化到一个版本中,可以帮助我们很好地了解相邻版本间的变动关系。
而且版本控制系统有撤销的功能,我们可以基于某个版本号,撤销所有/部分的变动信息,回到当时的文件状态。在项目的每一个重要阶段,认识和正确地使用撤销功能会让我们的工作变得非常轻松。
协同合作
基于传统方式修改项目代码的时候,必须告知团队中的其他人,我在干什么,防止他们和我冲突,而这不可能的,所以当我好不容易编辑完文件后,发现该文件被人删了,感觉很不舒服。
有了版本控制系统,团队每一个成员都可以自由的修改代码文件,进行团队的协同工作,版本控制系统可以帮我们将所有改动内容合并保存为一个版本,即使某些功能代码意外丢失,也可以通过版本系统找回。
版本控制系统分类
随着互联网的发展,软件产品的更新迭代越来越快,软件产品代码的版本控制系统也发生了千变万化,既有开源的,也有商用的,而且都是针对各种不同的应用环境设计的。目前市场上出现比例较高的版本控制系统主要有三类:本地版本控制系统,集中式版本控制系统和分布式版本控制系统。
本地版本控制系统(RCS)
许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别,这么做唯一的好处就是简单,但是特别容易犯错, 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。
其中最流行的一种叫做 RCS,现今许多计算机系统上都还看得到它的踪影。 RCS的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
集中化的版本控制系统
接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作?
于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 多年以来,这已成为版本控制系统的标准做法。
这种做法带来了许多好处,特别是相较于老式的本地 VCS 来说。 现在,每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。
事分两面,有好有坏。 这么做最显而易见的缺点是中央服务器的单点故障。 如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。
如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据——包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。 本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
分布式版本控制系统
于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。
在这类系统中,像 Git、Mercurial、Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。
集中式VS分布式
先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器,中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,这还不得把人给憋死啊。
那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上,既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了,而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
Git简介
Git是什么?
Git是目前世界上最先进的分布式文件版本控制系统(没有之一)。对于我们java程序员而言,管理的就是代码文件版本。
git是一个和svn一样的版本控制软件,但是与svn不同的是,git是一个分布式的高效版本控制系统。
其实现原理跟svn也大相径庭,采取了一种以空间换时间的理论,为什么是使用分布式呢,因为git会在每个开发者的本地中都保留了一份仓库副本,即使在断网的时候,也能提交代码到各自的仓库中,等联网后,再提交到中央仓库,每个开发者的仓库都是互相不可见的。
Git历史
Git 诞生于 Linux 内核社区对可用的 VCSs(版本控制系统)的挫败感
Linux 内核的发展在当时是相当不寻常的:项目中有大量的贡献者而且贡献者的参与程度和对代码知识库的了解有很大的差异。由于 Linux 内核不寻常的发展状况,开发人员很难找到适合他们需求的 VCSs(版本控制系统)。于是他们选择了 BitKeeper 和并发修订系统(CVS),每个系统有一组核心开发人员去负责管理内核的开发。BitKeeper 提供分布式版本控制,而 CVS 是一个客户端-服务端版本控制系统,它可以让开发人员“签出”项目的副本,进行更改,然后将他们的改变“签入”到服务端。
在 2005 年初期,BitKeeper 的版权持有人 Larry McVoy 宣布撤销允许免费使用 BitKeeper 软件的许可。他声称,正在创建与 BitKeeper 反向交互软件的澳大利亚程序设计师 Andrew Tridgell 反向设计了 BitKeeper 的源代码,这样违背了它的许可。许多依赖 BitKeeper 免费软件去开发 Linux 内核的 Linux 核心开发者现在已经无法继续使用它了。
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
集中式(SVN)
SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以首先要从中央服务器哪里得到最新的版本,然后干活,干完后,需要把自己做完的活推送到中央服务器。集中式版本控制系统是必须联网才能工作,如果在局域网还可以,带宽够大,速度够快,如果在互联网下,如果网速慢的话,就郁闷了。如果中心服务器出现问题,所有人都不能正常干活,恢复也很麻烦,因为SVN记录的是每次改动的差异,不是完整文件
下图就是标准的集中式版本控制工具管理方式:
集中管理方式在一定程度上看到其他开发人员在干什么,而管理员也可以很轻松掌握每个人的开发权限。
但是相较于其优点而言,集中式版本控制工具缺点很明显:
服务器单点故障
容错性差
分布式版本控制(GIT)
Git是分布式版本控制系统,那么它可以没有中央服务器的,每个人的电脑就是一个完整的版本库,这样,工作的时候就不需要联网了,因为版本都是在自己的电脑上。既然每个人的电脑都有一个完整的版本库,那多个人如何协作呢?比如说自己在电脑上改了文件A,其他人也在电脑上改了文件A,这时,你们两之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
下图就是分布式版本控制工具管理方式:
Git有什么特点?
Git和SVN区别
直接记录快照,而非差异比较
Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方式。
从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar 等等) 将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (它们通常称作 基于差异(delta-based) 的版本控制)。
存储每个文件与初始版本的差异: https://git-scm.com/book/en/v2/images/deltas.png
Git 不按照以上方式对待或保存数据,反之,Git 更像是把数据看作是对小型文件系统的一系列快照。 在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件, Git 对待数据更像是一个 快照流。
这是 Git 与几乎所有其它版本控制系统的重要区别, 因此 Git 重新考虑了以前每一代版本控制系统延续下来的诸多方面, Git 更像是一个小型的文件系统,提供了许多以此为基础构建的超强工具,而不只是一个简单的 VCS。
近乎所有操作都是本地执行
在 Git 中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其它计算机的信息。
如果你习惯于所有操作都有网络延时开销的集中式版本控制系统,Git 在这方面会让你感到速度之神赐给了 Git 超凡的能量, 因为你在本地磁盘上就有项目的完整历史,所以大部分操作看起来瞬间完成。
小结
- svn是集中式的,而git是分布式的,如果svn的中央仓库代码被删除了,那么可能代码真的就找不回来了,而git因为是分布式的,本地都有着所有代码的副本,所以即便中央仓库代码丢失,也可能通过本地代码重新恢复回来。
- svn每次提交记录,都是将提交的数据之间的差异数据进行保存,而git则是对有修改的文件使用另一个新的文件来保存,即使用了更多的资源,但是现在的社会,最不缺的就是空间资源了。
- svn服务器中使用了全局版本号,每次提交都会产生一个唯一的全局id,且是由顺序的。而git则是根据sha1来进行盐值加密算法获取,没有什么先后区分
- 分支管理的不同,svn的开辟新分支,则是将原有的分支的文件全部拷贝一份到新分支中,如果项目比较大,该过程可能会消耗点时间。而git则是通过指针的方式,非常的快速
- 操作的不同。svn中一般提交代码和拉取代码两步骤,而git则有一个暂存区的概念,先add,然后commit。
- 学习曲线的不同。svn相对简单,git学习曲线相对陡峭
为什么要用Git
- 首先git是一个比svn更加优秀的代码管理工具,已经可以说取代了svn,其区别如上
- 目前的很多程序中,都需要有git的支持,可能在使用一款工具时,会先检测是否安装了git,否则必须要求先安装git,可见其活跃度
- 由于github和码云的兴起,拉去代码都是通过git来操作完成
Git和GitHub
什么是GitHub?
确切的说 GitHub 是一家公司,位于旧金山,由 Chris Wanstrath, PJ Hyett 与 Tom Preston-Werner 三位开发者在2008年4月创办。这是它的 Logo:
2008年4月10日,GitHub正式成立,主要提供基于git的版本托管服务。一经上线,它的发展速度惊为天人,截止目前,GitHub 已经发展成全球最大的开源社区。 所以 Git 只是 GitHub 上用来管理项目的一个工具而已,但是GitHub 的功能可远不止于此!
Git,GitHub与GitLab的区别
- Git是一种版本控制系统,是一种工具,用于代码的存储和版本控制。
- GitHub是一个基于Git实现的在线代码仓库,是目前全球最大的代码托管平台,可以帮助程序员之间互相交流和学习。
- GitLab是一个基于Git实现的在线代码仓库软件,你可以用GitLab自己搭建一个类似于GitHub一样的仓库,但是GitLab有完善的管理界面和权限控制,一般用于在企业、学校等内部网络搭建Git私服。
- GitHub和GiLlab两个都是基于Web的Git远程仓库,它们都提供了分享开源项目的平台,为开发团队提供了存储、分享、发布和合作开发项目的中心化云存储的场所。从代码的私有性上来看,GitLab 是一个更好的选择。但是对于开源项目而言,GitHub 依然是代码托管的首选。
Git安装
下载
下载地址:https://git-scm.com/download
安装git for windows
双击安装:
基本上一路“Next”使用默认选项即可。
安装完成后,可以在任意文件夹点右键,看到如下菜单:
安装好Git之后在控制台输入Git 出现以下就是安装成功了
安装git for linux
查看是否安装了git
使用命令rpm -qa|grep git
若已经安装,需要先卸载。卸载命令如下: rpm -e –nodeps git 或者 rpm -e git
安装Git
输入命令:yum install git
输入y回车。 确认安装
再使用 rpm -qa|grep git
来查看是否已经安装好了Git
创建Git库
- 在需要的位置创建一个裸仓库(最后以.git结尾)
1 | cd /usr/ |
- 创建一个git用户并赋予密码
1 | useradd gituser |
- 赋予git用户权限
1 | chown -R gituser:git /usr/git/ |
更新git for windows
- git版本是2.17.1之前
1 | git update |
- git版本是2.17.1之后
1 | git update-git-for-windows |
配置 Git
安装完成Git后还需要进行一些配置
用户信息
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。
这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,并且不可更改
1 | git config --global user.name "John Doe" |
如果使用了 --global
选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息,当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有 --global
选项的命令来配置
1 | git config user.name "John Doe" |
文本编辑器(可选)
设置完成用户信息后,接下来就可以设置文本编辑器了,如果git需要输入相关信息就会调用该编辑器,如果未配置,Git 会使用操作系统默认的文本编辑器
在 Windows 系统上,如果你想要使用别的文本编辑器,那么必须指定可执行文件的完整路径
下面是配置Git的默认编辑器是notepad++
1 | git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin" |
查看配置信息
如果想要检查你的配置,可以使用
git config --list
命令来列出所有 Git 的相关配置
1 | git config --list |
Git的基本使用
创建版本库
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。由于git是分布式版本管理工具,所以git在不需要联网的情况下也具有完整的版本管理能力。
创建一个版本库非常简单,
1)首先,选择一个合适的地方,创建一个空目录。我在本机的D:\\test
目录下,创建了一个Hello目录:
2)使用git init
命令把这个目录变成Git可以管理的仓库:
命令输入后,会提示你,已经创建了一个空的Git仓库。此时你会在hello目录下发现一个隐藏目录.git
这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。
此处的hello目录就是我们的:工作区,存放所有当前文档。此目录下的文件才会被Git管理
hello中的.git
目录就是我们的:本地仓库,管理并保存所有的文档变化及历史状态。
总结:创建版本库的步骤:
1) 进入需要管理的目录
2) 执行 git init
命令
添加文件并提交
版本控制系统,其目的就是跟踪文本文件的改动,例如我们开发时编写的.java
、.xml
、.properties
本质都是文本文件。文件中每一个字符的变化都会被跟踪并且管理。
1)我们在当前的hello目录下创建一个新的文本文件:readme.txt
编写一段文字(注意,一定不要用windows的记事本):hello git
2)接下来,我们使用 git add
命令,将文件添加到暂存区
git add 文件名
没有任何的反应,证明没有问题
3)使用 git commit
命令,将暂存区文件提交到本地仓库
如果是第一次安装使用git,提交的时候需要认证用户
命令解释:
git commit 命令可以将暂存区的文件提交到版本库。
-m 参数,是本次提交的说明信息,用来注释本次提交做了些说明事情。
总结,将一个文件添加到本地仓库,分两步:
3) 使用 git add <file>
命令,添加文件。可以一次添加多个文件。
4) 使用 git commit 命令,提交,一次即可。
可能大家会有疑问,为什么这里不是直接commit提交,而是要经过add和commit两个步骤呢?
这就关系到Git的版本库中的 工作区 暂存区概念了。
工作区/暂存区/版本库
我们先来理解下Git 工作区、暂存区和版本库概念
工作区
工作区就是你在电脑里能看到的、存放代码的目录。比如我们刚刚创建的hello目录:
其中包含了一个隐藏目录 .git
,其它就是我们需要被管理的文件。
暂存区
暂存区:用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
英文叫 stage 或 index,一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)
版本库
工作区有一个隐藏目录 .git
,这个不算工作区,而是Git的版本库。
版本库就是安全存放数据的位置,这里面有你提交到所有版本的数据,其中HEAD指向最新放入仓库的版本,之所以说git 快,是因为它是分布式版本控制系统,大部分提交都是对本地仓库而言的,不依赖网络,最后一次会推送的到远程仓库
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
远程仓库
托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换,比如GitHub,Gitee等
管理文件修改
被版本库管理的文件不可避免的要发生修改,此时只需要直接对文件修改即可。修改完毕后需要将文件的修改提交到版本库。
我们对readme.txt文件进行修改,添加一行数据:
差异比较
用git diff -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别:
可以发现,与版本库中的 readme.txt相比,我们多了一行文本!
查看状态,提交修改
我们如果不确定自己的哪些文件被修改了,可以使用git status 命令,查看当前工作区的状态:
可以清楚的看到:changes not staged for commit(修改没有被缓存,需要使用git add来进行添加操作)
我们使用 git add
命令,添加到暂存区,然后再次查看状态:
提示说:工作区很干净,没有任何需要提交,搞定!
版本回退
现在,我们再次修改readme.txt,添加一行内容:
然后提交到版本库:
日志查看
我们通过 git log 命令,可以查看历史的每次提交信息:
可以发现,目前为止,我们已经在本地仓库中提交了3次,也就是说有3个不同版本。其中,最近的这个版本有一个标示:HEAD ,这就是标记当前分支的当前版本所在位置。
本例当中,当前版本即 test version control这次提交,如果没有 HEAD->MASTER
,加上 --decorate
查看,即git log --decorate
。
另外,在log中,每一个版本的前面,都有一长串随即数字:2edf728e6fde09c9d33ce6dd96fd684ed09ebcc ,这是每次提交的commit id ,这是通过SHA1算法得到的值,Git通过这个唯一的id来区分每次提交。
版本回退
现在,假设我们要回到上一级版本,该如何操作呢?
首先,Git通过HEAD来判断当前所在的版本位置。那么上一个版本,就用HEAD^标示,上上一个版本就是 HEAD^^
,当然往上100个版本写100个 ^
比较容易数不过来,所以写成HEAD~100。
如果要从 “test version control” 回退到 “modify readme file” ,我们可以使用 git reset
命令
提示说:HEAD 现在已经被设置到 35CEB36 的版本,即 modify readme file。
我们查看readme.txt:
果然,版本已经回退了,最新添加的数据“test version control”已经没了。
此时再次查看日志,发现只剩下2次提交信息了,第三次提交的信息已经没了:
假如此时我后悔了,还想回到第3次提交的版本,怎么办?
查看所有关联日志
我们可以通过git reflog
命令,看到以前的每次执行动作:
其中红框内的部分,就是我们第三次提交的日志信息。前面的e498642 就是第三次提交的 commit id 的前几位数字。
我们可以通过指定commit id 的方式,来指定HEAD的位置:
指令:git reset --hard {commit id}
查看日志:
查看文件:
数据又回来了!
总结
如果要进行版本回退或前进,一般分两步:
1) 通过git log 或 git reflog 查看操作日志,查找版本的commit id
2) 通过 git reset --hard <commit id>
设置HEAD到指定版本
其实版本的回退,仅仅是修改HEAD指针的位置而已,因此Git进行版本的切换,比svn要快的多!
撤销修改
工作区 -> 暂存区 -> 版本库
撤销工作区修改
现在我们在readme.txt中添加一行数据:
在你提交前,你突然发现这个修改是有问题的,你打算恢复到原来的样子。怎么办?
如果修改的并不多,我们完全可以手动恢复到原始状态。但是如果改动比较大,手动处理就很容易有遗漏,而且很麻烦。怎么办?
查看状态:
Git提示我们,现在文件已经修改,等待被staged(暂存)。我们有两个选择:
1) 可以使用git add 来添加到暂存区,接着去提交文件
2) 可以使 git checkout -- <file>
来撤销修改
所以,这里我们选择第二种方案后,再次查看状态:
工作区是干净的!修改已经被撤销了!
查看文件:
撤销staged修改
刚才的案例中,我们修改了数据,并没有add带暂存区,处理起来还算简单。如果我们已经吧数据add 到了暂存区,又该如何处理呢?
我们首先添加一行数据到readme.txt
并且添加到staged(暂存区),然后查看状态
有一个修改等待被提交,并且有一行提示:
可以使用 git reset HEAD <file>
来撤销缓存修改。
我们前面说过,git reset 命令可以进行版本回退,此处reset 指定的是HEAD ,而不是其他版本,因此就有撤销缓存修改的作用:
查看状态:
发现文件的修改被撤回到了工作区,尚未添加到staged(暂存区),我们再次执行 git checkout -- <file>
即可撤销工作区修改
工作区干净了!
查看文件:
文件也恢复了原来的状态,整个世界都清净了!
总结
撤销修改分两种情况:
1) 撤销工作区修改,使用 git checkout -- <file>
2) 撤销暂存区修改,分两步:
a) 使用git reset HEAD <file>
来撤销暂存区修改。
b) 使用git checkout -- <file>
来撤销工作区修改
Git Commit注释标准化
前言
Git Commit Message 应该清晰明了,要用精简的语言说明本次提交的目的,其主要作用是为了后续的搜索、版本的回滚、合并冲突的追溯等操作。
我们在开发时一直以来对 Git Commit 格式有个约定俗称的要求,所以就没落实明确的规范。
因为没有明确的规范,就会导致提交的消息较为随意。甚至出现「“.”、”Update”」这样的消息。
直到我在 GitHub 上发现了这条 Commits 时,才意识到提交信息也该规范起来。
以下图举例,当代码出现 Bug 时,应该回滚到哪个版本?
回滚到 “朕与将军解战袍,芙蓉暖帐度春宵” 吗?
这条记录所变更的内容是啥,看概要我一概不知。 ︿( ̄︶ ̄)︿
为了解决规范问题,我参考了一些的开源项目,当发现 commitizen 库时,才知道好多大型开源(AngularJS、VueJS)项目早已使用了它。所以在接下来我会介绍一下 commitizen 工具所使用 Google AngularJS 规范。
规范介绍
这次主要介绍 AngularJS 的规范,它是由 Google 推出的一套提交消息规范标准,也是目前使用范围最广的规范。有一套合理的手册也较为系统化;并且还有配套的工具可以供我们使用。
说白了,规范就是用工具进行强约束。单看规范比较简单,所以先让大家先看看面,知道他的大体规则后,在来讲细节。
规范执行方案如下:
既然有了方案,就会按照某些规则执行,以下是 Google AnguarJS 规范的要求:
规范目标
允许通过脚本生成 CHANGELOG.md
可以通过范围的关键词,快速的搜索到指定版本
1 | git log HEAD --grep feat(package.json) # 在package.json文件里新增的特性。 |
格式要求
1 | <type>(<scope>): <subject> |
消息只占用一行,任何行都不能超过 100 个字符
允许使用 GitHub 以及各种 Git 工具阅读消息
提交消息由页眉、正文和页脚组成,由空行分隔
<type>
代表某次提交的类型,比如是修复一个 bug 或是增加一个 feature,类型如下:
<scope>
范围可以是指定提交更改位置的任何内容,如:
对 package.json 文件新增依赖库,chore(package.json): 新增依赖库
或对代码进行重构,refacto(weChat.vue): 重构微信进件
<subject>
如果没有更合适的范围,可以直接写提交内容
Commit 实战
提交一条依赖库变更,type 为 chore(增加依赖库);等提交完成后,使用 Git 工具进行搜索。
此时搜索类型是 chore(package.json),所以就能知道 package.json 文件所有的历史变更。
1 | # 新增一条 Commit 记录 |
工具介绍
因为是 Google AngularJS 的标准规范,所以提供了多种工具。如生成 CHANGELOG.md,提交工具,检查工具。
工具列表:
- 提交工具 commitizen,如果是初学者,可以使用 commitizen 帮助我们生成消息
1 | 或者 |
- 生成 CHANGELOG.md,把 Git Commit Message 的消息自动生成 CHANGELOG.md
1 | npm install -g conventional-changelog-cli |
- Message 检查,是否有 “不符合” 规范的内容,可以在 GitHook 中使用
提交以及检查工具相对来说简单,大家自学即可,所以我以生成 CHANGELOG.md 举例。
1 | # 安装 CHANGELOG 生成器 |
文档生成后,当前目录下就有 CHANGELOG.md 文件了,如果是 Node 项目,也会自动更新 package.json version 的版本号
这是根据 Git Commit Message 历史记录所生成的 CHANGELOG.md,在也不用手写了。( ̄▽ ̄)“
分支管理
分支有什么用
假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险.
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
Git的分支管理原理
我们的每次提交,都对应一个具体的时间点,git会把这许多的时间点串起来,就形成了一条时间线,这条时间线就是一个分支。Git中默认的分支就是主分支,叫master。
我们查看当前的提交日志:
发现总共有3次提交,这3次提交可以串起来成一条时间线,就是master分支:
每次提交,master分支都会新增一个时间点,分支线也不断变长。
当我们创建新的分支,例如dev分支。Git会创建一个新的指针,叫做dev,指向跟master相同的时间点(提交点),这样分支就创建好了,你的工作区无需任何改变,创建分支的速度非常的快。
而要切换分支,只需要把HEAD指向dev即可,所以你的分支实现了光速切换!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
你会发现Git的分支管理,基本就是创建新的指针,改变HEAD指向,删除指针等操作,几乎没有文件的增删。所以速度非常快!
分支的创建和合并
创建分支
我们可以使用 git checkout -b 分支名 来创建并切换到新的分支:
你会注意到我们已经切换到了dev分支。 git checkout 加上 -b 参数,就等同于创建分支,并切换分支。相当于以下两条命令:
1 | git branch dev # 创建分支 |
使用git branch 查看所有分支,当前分支前面会有一个*
表示:
然后我们可以在dev分支上进行修改和提交。例如我们在readme.txt上添加一行文字:
提交修改:
此时,dev分支已经比master领先了一次提交,并且HEAD指向的是dev
合并分支
我们使用git checkout master切换回master分支,查看内容:
发现readme并没有改变,因为刚才修改的是dev分支。此时的HEAD已经指向了master了:
我们使用git merge dev命令将 dev分支的修改合并到master分支:
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
删除分支
合并完成后,就可以放心地删除dev分支了,可以使用git branch -d dev 命令删除dev分支,dev就是具体的分支名
再次查看分支列表:
总结
1) 使用git branch 分支名 创建分支
2) 使用git checkout 分支名 来切换分支
3) 也可以使用 git checkout -b 分支名 来完成 创建并切换分支的操作
4) 使用git merge 分支名 来合并分支到当前分支
5) 使用git branch -d 分支名 来删除指定分支,注意:要删除一个未合并的分支。需要使用-D
参数进行强制删除
解决冲突
制造冲突
现在我们新建一个分支:dev
然后修改readme.txt:
将dev的修改提交:
切换到master分支:
并且在readme.txt最后添加内容:
提交数据:
现在,master和dev都有了各自新的提交,变成了这样:
解决冲突
这种情况下,是无法进行快速合并的。我们试一下:
自动合并失败,必须先解决文件冲突,才能提交。
此时查看readme.txt文件:
我们可以根据实际情况进行冲突解决,比如两者都保留:
然后再次提交:
工作区就干净了。此时master和dev分支线就变成了这样:
可以用git log –graph –decorate –pretty=oneline –abbrev-commit命令来查看:
接下来就可以删除dev分支了。
客户端工具TortoiseGit
现在Git的客户端工具非常多,比较流行的例如:TortoiseGit(在svn中俗称小乌龟)、SourceTree。
SourceTree的注册需要科学上网。因此这里就不做讲解了。
安装
安装TortoiseGit
双击运行安装:
一路“Next”使用默认选项即可。
默认选项下会启动配置画面:
由于目前只有英文语言包,默认即可继续下一步。
配置git.exe,在4.2.1中已经安装过git-for-windows了所以在此找到git.exe所在的目录。
配置开发者姓名及邮箱,每次提交代码时都会把此信息包含到提交的信息中。
使用默认配置,点击“完成”按钮完成配置。
完整完毕后在系统右键菜单中会出现git的菜单项。
安装中文语言包(可选)
安装中文语言包并不是必选项。可以根据个人情况来选择安装。
双击运行:
直接“下一步”完整完毕。
鼠标右键选择Settings
语言包安装完毕后可以在TortoiseGit的设置中调整语言
基本使用
创建本地仓库
我们新建一个空的文件夹:
然后进入tortoise目录,右键操作:
弹出提示,不要勾选:
查看目录,发现生成.git文件夹:
添加文件并提交
创建新的文件:
编写内容:
在文件夹中右键操作:
提示:这一步等同于我们的 git add readme.txt
此时直接点击提交,即可完成:git commit 操作:
提示:
管理修改
差异对比
修改readme.txt:
右键操作:
结果:
提交修改
直接在文件上选择右键,提交即可(只要add过一次,后续不用add操作,直接提交):
查看提交日志
选中文件,右键菜单中,选中查看日志:
提交的日志信息:
版本回退
现在我们再次修改readme.txt,并且提交
查看日志:
假如我们要回到上一个版本,也就是第2次提交。
我们选中第2次提交,然后右键,选中:重置”master”到这个版本
弹出菜单,这里选中Hard模式,然后确定:
再次查看日志,只剩下第1和第2次提交了。并且HEAD已经设置到了第2次提交位置
文件也回滚了:
如果我现在后悔了,想再次回到第3次提交怎么办?现在连日志都没有了!
此时,在空白处点击右键,选中 显示引用记录:
弹出所有操作的日志信息:
现在,我们找到第3次提交,右键,选中:重置“master”到这个版本
结果,第3次提交又回来了!
文件内容回来了:
撤销修改
我们现在修改文件:
现在后悔了,想要还原到修改以前。
我们可以选中文件,右键。然后选中菜单:还原。
点击确定:
还原成功:
查看文件:
访问远程仓库
我们创建一个新的仓库:
设置SSH
由于安装时,我们并没有设定SSH信息,因此默认tortoise默认使用的ssh工具是“PuTTY”。
然而,git Bash使用的ssh工具是“openSSH”,如果想让TortoiseGit也使用刚才生成的密钥可以做如下配置:
找到git安装目录下的ssh.exe文件:
关联远程仓库
这里的四个选项:
1) 远端仓库名称,一般叫origin
2) URL:远程仓库地址 ssh://gituser@192.168.154.133:22/fangqyou/tortoise.git
3) 推送URL:同上 ssh://gituser@192.168.154.133:22/fangqyou/tortoise.git
4) Putty密钥:我们用git bash 生成的私钥。
推送本地仓库
在空白处点右键,选择 “Git同步”:
弹出菜单中,选择将master推送到远程仓库的master:
成功:
私服中也显示了最新的信息:
从远程仓库拉取
现在,我们先在远程仓库修改数据:
然后在本地的tortise文件夹点击右键,菜单中选择:拉取:
成功:
查看文件:
分支管理
创建分支
在文件夹的空白处点击右键。选择创建分支:
填写分支名称和说明:
查看日志,发现已经有了dev分支:
切换分支
在空白处选择右键,菜单中选择: 切换/检出
选择要切换的分支:
在dev分支中。修改readme文件:
提交修改。
然后切换到master:
查看文件内容,发现并没有变化,因为刚才的修改是在dev完成的。master没有影响。
合并分支
空白处点击右键,选择合并菜单
选择将dev合并当当前分支:
成功:
查看内容:
解决冲突
切换到dev,然后进行修改
提交数据。
切换到master,修改readme:
然后提交修改
尝试用master合并dev:
结果失败了,因为有冲突:
查看文件:
手动解决:根据需求去处理。这里我们假设两者都保留:
标记为解决:
解决完成,直接提交:
提示信息:
提交成功!
查看日志:
将Java工程加入到版本库
现在,我们有一个准备好的maven项目,一个用户管理系统:usermanage
我们要吧这个项目交给git去管理。
初始化本地仓库
忽略文件和目录
将不需要管理的文件和目录忽略,maven项目中需要提交的只有src和pom.xml,其它文件和目录都应该忽略:
选择递归忽略:
同样的方式处理target目录等其他需要忽略的文件。
大家会注意到,在本地仓库目录,多出了一个.gitignore文件,这里面就定义了所有的过略规则:
所有文件添加到暂存区
方式1:使用git bash 。 敲击命令: git add . 就会把当前目录所有文件加入暂存区
方式2:使用tortoise:
你会发现,待添加的文件,只有src目录下的和pom.xml,当然还有那个.gitignore文件:
成功:
提交
然后提交即可:
成功
有必要的话,推送到远程
客户端工具gitextension
下载:http://gitextensions.github.io/
安装:下一步next
Idea客户端
在Idea中配置Git
打开File菜单:
在File –> Setting->Version Control –> Git –>Path to Git executable选择你的git安装后的git.exe文件
打开Setting:
方式一:
方式二:
配置本地安装的Git的git.ext文件:
然后点击Test,测试是否设置成功
创建工程
我们新建一个maven工程:
编写简单的代码:
将项目创建为本地仓库
打开VCS菜单
VCS –> Import into Version Control –> Create Git Repository
在弹框中选中项目所在的位置,点击OK。
此时项目文件全部变成棕色。
项目Git版本已经创建成功。
忽略文件
安装ignore插件,在file->settings->plugin搜索.ignore,点击Install,安装完成后就可以愉快的使用了,记得重启IDEA。
可以手动创建和修改.gitignore文件,也可以通过插件过滤。
提交代码
添加到暂存区
项目右键选择Git –> add
此时项目文件全部变成绿色,此时文件只是处于暂存区,并没有真正进入到版本库中。
提交到本地仓库
项目右键Git–> Commit Directory
在弹窗中输入Commit Message,点击commit,此时项目文件从暂存区真正进入版本库中,项目文件变成黑色。
编辑本次提交备注信息,然后点击commit按钮。
此时项目文件全部变成黑色:
提交远程仓库
新建一个远程仓库
在远程仓库创建新项目:
记录地址:
推送到远程仓库
右键选择项目—> Git ->Repository -> Push ,然后填写远程仓库地址。
复制远程仓库的地址,并填写:
点击Push, 推送成功, 在Idea右下角弹出提示框:
查看远程仓库,推送成功。
拉取数据
在远程仓库随意修改代码:
在项目中,拉取代码:
点击Pull:
代码成功同步:
克隆项目
删除本地项目
Settings->Version Control
1)断开idea与Git版本仓库的联接:
- 从Idea和本地文件目录中删除项目。
克隆远程仓库并导入Idea
打开Idea,点击 Check out form Version Control,选中Git
填入远程仓库SSH地址,输入你的远程仓库地址,点击Test,测试一下地址是否正确。
点击Clone
点击YES:
Clone出远程仓储的工程,并且导入到idea中。
导入成功:
右键Git,可以与远程仓库进行push和pull代码操作了。
解决冲突
制造冲突
1)我们创建新的分支dev,并且在dev修改提交代码。
2)然后在master修改提交代码。
接下来尝试合并dev分支:
选择要合并的分支:
发现合并失败,此时文件有红色标记
解决冲突
点击Merge合并策略**
Accept Yours:保留你自己的代码,
Accept Theirs:保留别人的,
merge:人工合并 (人工把需要的代码复制粘贴到结果集result里面比较保险)
弹出一个对比页面:
在Result中,手动合并完成后,点击Apply按钮,完成冲突代码合并。
此时文件的红色标记没了:
但是有未提交的蓝色标记。然后提交,文件变为黑色。冲突被解决!
Git命令整理
1 | git init 创建一个空的仓库 |
Git Submodule
有种情况我们经常会遇到:
某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。
现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。
概念
submodule,子模块,其实就是另一个仓库(更新子模块不会自动更新主模块的引用)
基础操作
添加子模块
1 | 直接clone,会在当前目录生成一个someSubmodule目录存放仓库内容 |
clone已经包含子模块的项目
正常clone包含子模块的函数之后,由于.submodule文件的存在someSubmodule已经自动生成。但是里面是空的。还需要执行2个命令。
1 | 用来初始化本地配置文件 |
git submodule 工作流
当一个项目里面包含子模块的时候,不仅仅需要对父仓库进行版本管理,子模块目录下也是存在版本的。那在不同的父仓库下面如何进行子模块的版本管理也成为新的问题。
最简单的办法,就是主项目只专注使用子模块的master分支上的版本,而不使用子模块内部的任何分支版本。
1 | cd submodulePath |
此时在主项目就能看到submodule目录已经更新了。 当然这也操作有点不方便,下面是更简便的方法
1 | Git 将会进入子模块然后抓取并更新,默认更新master分支 |
如果需要更新其他分支的话,需要另外配置。
1 | 将git submodule update --remote 的分支设置为stable分支 |