摘要
今天我们就来说说Git版本控制吧,相信大家都有用或者将要用也或者曾经有用吧。如果你还没有使用Git或者对其没有了解的话,今天这篇博客非常适合你。这篇博客的主要内容从Git的原理、Git的配置、到Git的基本命令的使用及说明,然后使用Git命令在Github
创建一个完整的仓库;最后使用fork方式在Github
上参与开源项目及其背后的逻辑再对Git版本控制来作一个比较基础、全面的了解。
1.Git起源
在早期的Linux
开源项目中,有着众多的参与者,绝大多数的Linux
内核维护工作都花在了提交补丁和保存归档等繁琐,重复的事务上。到2002年,整个项目组开始启用分布式版本控制系统BitKeeper
来管理和维护自己的代码,但好景不长,到2005年BitKeeper
公司就收回了Linux
开源团队免费使用BitKeeper
的权力。这就迫使Linux
团队不得不更换版本控制系统。此时,Linux
之父Linus
决定自己开发一套版本控制系统。在开发系统之前,先对系统作了几个大致的要求:
1.速度
2.简单的设计
3.对非线性开发的强力支持(也就是允许成百上千的开发者同时开发)
4.完全分布式
5.有能力高效管理类似Linux
内核这样的大型项目
有了目标之后,事情就变得好办了。身为Linux
之父的Linus
花了两周时间就用C完成了一个分布式版本控制系统,这就是Git
的第一版!经历一个月之后,Linux
内核源码就由Git
来管理了。
如今,Git已经非常成熟,越来越多的公司都使用Git
作为自己的版本控制系统来管理代码,特别是开源部分,世界上最大的开源网站Github
就是基于Git
来让开发者管理开源项目的。
2.Git基本原理
像我这一代的程序员除了Git
外就只用过SVN
吧。个人感觉在国内这两个占主流。两者虽然在操作上非常相似,但是两者在实现上有着本质的区别。Git
属于分布式版本控制系统,而SVN
属于集中式版本控制系统。Git
的每一个节点都是平等的,不需要依赖其它任何节点,就可以完成对代码的修改、记录与提交,最后再将本地的代码提交到主干来进行合并。而SVN
依赖于中心服务器,如果与中心服务器断开,就不能对代码进行版本控制。接下来就来了解一下Git的基本原理吧!
2.1.直接记录快照,而非差异比较
Git
与SVN
的差别在于:Git
只关心文件数据整体是否发生变化,而SVN
只关心文件内容的具体差异。SVN在每个版本下对有变化的文件 进行一次记录,没有变化的不处理。
而Git更像是把变化的文件做快照后,记录在一个微型的文件系统里。每次提交更新时,Git都会去遍历一次所有文件的指纹信息并对文件做统一快照,然后保存一个指向这个快照的索引。为了提高性能,若文件没有发生变化,也就是指纹信息没有改变,
Git
不会再次保存,而只对上次保存的快照做一个链接。
2.2近乎所有操作都在本地执行
在使用Git
来进行版本控制时,一般会先从服务器上pull
一份项目源代码保存在本地,这份本地的源代码与服务器上的可以说是cp下来完全一样的。之后在项目中的操作基本上只需要同这个本地仓库来进行操作,就可以对代码进行版本记录与控制。也就是说代码成功cp之后,基本上就不需要网络,就可以对当前代码进行版本控制。而SVN
几乎所有的版本控制的操作都必须要在有网络的情况下进行。这也就是为什么Git
比SVN
要快得多。
举个例子,如果你需要查看某个文件的历史记录,Git
不需要到服务器上去取数据,而是直接从本地数据库里记取后展示给你看。如果想要查看当前版本的文件与一个月前的版本文件之间的差异,Git
会取出一个月前的快照与当前文件做一个差异计算,而不用去请求服务器,或者是把老版本的文件拉到本地来做比较。Git
的本地化操作使开发对代码的代码管理非常方便,无论你身处何处都能完成对代码进行版本控制,这样极大的提高的开发的效率。
2.3时刻保持数据完整性
在保存Git
之前,会对所有的数据进行内容的校验和计算,并将结果作为数据的唯一标识和索引。如果文件变得不完整,比如:文件传输没有完成、磁盘损坏导致的数据丢失等,Git
都会通过前后SHA
之间的比对而立即察觉。Git
是通过SHA-1
算法计算数据的校验和,通过对文件内容或目录结构计算出一个SHA-1
的哈希值,作为指纹字符串,该字符串由40个十六进制字符(0-f)来进行表示的。看进来像这样。Git
的实现完全依赖于此指纹字符串,我们会在各种场合下看到类似的字符串。比如通过在Git
项目中使用Git log
命令来查看提交历史时,就能看到相应的指纹字符串。
2.4多数操作仅添加文件
这里要说的是,大多数的Git
操作可能都是向数据库添加数据,而像删除数据等操作就需要谨慎,因为这种操作是一种不可撤销的操作,一旦操作失误,就无法进行回退和恢复。这里就需要我们养成定期向远程仓库提交代码的习惯,这样的话,即使本地数据不小心被误删,也可以从远程仓库来进行恢复。
2.5文件的3种状态
要了解Git
的工作模式,就必须先了解在Git
管理下的项目的3种状态。对于任何一个文件,在Git
内部只有3种状态:
- 已提交(committed):表示该文件已经被安全地保存在本地数据库。 即执行了
git commit
命令。- 已修改(modified):表示已修改了某个文件,但还没有提交保存。
- 已暂存(staged):表示已修改的文件放在下次提交时要保存的清单中,也就是执行了
git add
命令。
由此图可以看出Git管理项目时的,文件流转的三个区域:
- Git工作目录:Git对哪个目录进行版本控制,那么这个目录就叫做工作目录。工作目录里的数据实际上都是从Git目录的压缩对象数据库中提取出来的。
- 暂存区域:只不过是一个简单的文件,一般都放在Git目录中。也就是执行了
Git add
命令后文件添加到下次提交要保存的清单的存放位置。- 本地仓库:将数据存放到数据库文件中进行保存。
每个项目都有一个Git目录(当前项目根目录下隐藏的.git目录),这是Git用来保存元数据和对象数据库的地方。每次克隆镜像仓库时,实际上复制的就是此目录的内容。
基本的Git工作流程
1.在工作目录中修改某个文件
2.对修改后的文件进行快照,然后保存到暂存区域
3.提交更新,将保存在暂存区域的文件快照永久转储到数据库中
3.Git的基本配置
Git
提供了一个Git config
的命令行工具来对Git
进行配置。通过Git config
配置的参数可以保存到操作系统三个不同的地方,各自代表不同的意思。
/etc/gitconfig
:表示系统中对所有用户都普遍适用的配置。若使用git config
时用–System选项,读写的就是这个文件。~/.gitconfig
:用户目录下的配置文件只适用于该用户。若使用git config
时用--global
选项,读写的就是这个文件。- 当前项目下的
.git/config
:该配置仅仅针对当前项目有效。也就是使用git config
不带参数选项。- 每一个级别的配置都会覆盖上层的相同配置,覆盖的优先级为3>2>1
接下来就说几个比较重要的配置。
第一个需要配置的不是你的个人用户名称和电子邮箱地址。每次Git
提交时都会引用这两条信息,说明是谁提交了一时更新。
git config –global user.name “yourname”
git config –global user.email yourname@example.com
如上所示,如果命令中包含了--global
则表示更改的配置文件位于当前用户主目录下的那个。以后你所有的项目都会默认使用此配置里的用户信息。如果在某个特定项目想要更改用户信息的话,就使用上面去掉--global
的命令,这样,配置信息就会保存到当前项目的.git/config
文件中。
第二个比较常用的配置就是,在解决合并冲突时使用哪种差异分析工具。这里可以使用的差异工具有kdiff3
、tkdiff
、meld
、xxdiff
、emerge
、vimdiff
、gvimdiff
、ecmerge
、opendiff
.
要查看已有的配置可以使用git conifg --list
来查询。这里列表了所有的配置信息。
另外还有一些配置高亮显示的命令:
git config –global color.status auto //查看状态时高亮显示
git config –global color.branch auto //分支名高亮
git config –global color.ui auto //自动高亮
最后一个可以提升你效率的配置就是命令的别名配置。例如:checkout
命令可以简化为co
,status可以设置为st
.设置别名命令如下:
git conifg –global alias.新名字 原始命令
如:将checkout
改成co
的命令为:
git config –global alias.co checkout
配置后就可以直接用git co
来代替git checkout
了。
4.Git基本命令
使用Git可以使用两种方式进行操作,分别为:Git
命令行和GUI
操作。但是为了了解Git
的工作原理,我们一定要先从命令行入手,就算了以后要使用GUI
进行操作也是在熟悉了Git
命令行的操作下进行。后面我们从Git
的常用命令行来一步一步的进行说明。
4.1 Git init(初始化)
Git init
是Git
进行操作的第一步,该命令是在项目根目录创建一个隐藏的.Git
目录。该目录存放了Git
的配置信息,Git
就是使用此目录来对项目进行版本控制的,也就是项目的快照以及其它的数据信息都是保存在此目录的。例如:我创建了一个名叫git_proect
的目录。在该目录下使用了git init
来进行初始化。
4.2 Git Status(查看当前状态)
在使用了Git init
进行项目的初始化之后,在Git Status
中使用最频繁的一条命令,此条命令的意思是查看当前项目的状态。例如:我现在在该目录下创建一个文件HelloWorld.java
,然后来查看一下当前的状态。图上比较显眼的就是红色部分,也就是我们创建的
HelloWorld.java
文件。这里显示红色表示当前文件还没有被添加到git管理。而红色框里的内容意思是使用git add
可以将任何文件添加到Git
进行管理。使用git add
也就是我们前面所说的三种状态下的已暂存。也就是先把当前文件或者目录添加到git文件提交清单中,下次commit
时被提交到git
本地仓库。
4.3 Git Add(添加文件到追踪列表)
Git add
方法的作用是将当前一个或者多个文件添加到Git
追踪列表中,也就是通过Git add
来将文件添加到Git
版本控制。一般情况下,git add
后面跟参数为一个文件或者.
。
- git add HelloWorld.java //将此文件添加到
Git
- git add . //将当前目录下的所有文件,包括子目录下的文件全部添加到
git
使用git add HelloWorld.java
之后,我们再来通过git status
来查看当前的状态变化。这里
HelloWorld.java
已经从红色变成了绿色,说明此文件已经被添加到了git。这里说明一下,平时要养成git status
来查看当前状态,以避免遗漏。
我们已经知道了怎么添加一个文件或者目录到git中,而当我们想要移除一个文件呢?我们该怎么操作?其实上面的()里面就已经有了说明,git rm --cached <file>
来对文件作移除操作。
4.4 Git commited(提交)
一般使用git add
添加文件到git
追踪目录中的时机是当一个功能已经被完成,并且想要添加到本地仓库中。使用完git add
添加文件后,我们需要使用git commited
来将暂存文件中的文件添加到本地仓库。此时,你的代码才是真正添加到了git本地仓库。一般,提交git
的方式有两种:
- git commit -m “提交说明文字” //通过-m在提交时添加简短说明
- git commit //不加说明信息,直接使用
git commit
提交,会跳转到编辑器。
使用第一种方式快捷、方便,不需要跳转到编辑器就完成了代码提交。但是当说明的内容比较多时,第一种方式就不太合适了,此时就需要使用第二种方式来添加单独到编辑器里进行内容的说明了。使用第二种方式,是这样子的。在没有
#
的空白区域添加要说明的文字,之后,保存。就完成了文件的提交。此时我们使用git status
来查看当前的状态。红框部分说明当前已经没有可以提交的文件。到这里说明,此次创建的文件就已经提交到了本地仓库。
4.5 Git log(查看历史提交记录)
git log
的作用是查看历史提交的记录,也就是查看某个文件被修改的记录。比如:我们上面添加了一个HelloWorld.java
的文件,此时我们使用git log
来查看当前项目的历史提交记录。上面的信息说明我们的项目只提交过一次,所以,只有一条提交历史记录。
4.6 Git clone(下载项目)
很多时候我们需要将别人托管在网站上的开源库下载到本地计算机,此时我们需要用到git clone
将远程代码库下载到本地。比如:我们需要下载DynamicAPK
这个项目到本地,可以使用
4.7 Git branch(不同分支)
此命令可以查看或者创建新的分支。在git_project
里,使用git status
查看当前状态,会发现命令行里都有一个master
标识。表明当前处理
master
分支。当Git
初始化之后,会默认创建一个master
分支,你的操作也默认在master
分支进行,不同的分支的文件相互不影响。我们可以通过git branch
来查看当前项目的分支情况,以及当前操作位于哪个分区。图上表明,当前项目下只有一个
master
默认分支。当你需要开发一个聊天的新功能,但是又没有太大的把握能够如期顺利的完成,为了安全起见,此时就需要新建一个新的分支,然后将聊天的功能代码在新建的分支下进行coding
。在图上我们创建了一个
talking
的分支,然后查看了当前的分支状态。带*
号说明我们当前所处的位置是在master
分支下。那么,我们需要到talking
来进行coding
要怎么办呢?此时我们需要使用git checkout
命令来进行切换。
4.8 Git Checkout(切换分支)
git checkout
的作用为切换分支和恢复数据。当我们需要切换到talking
分支时我们可以使用
git checkout 分支名(这里为talking)
来将当前位置切换到talking
分支。可以看到,当前分支已经切换到
talking
,也就是*
所指向的分支。此时我们在当前分支创建一个chat.java
的文件,然后在chat.java
里添加相应的实现聊天的代码。将chat.java
开发完成后,我们通过下面两条代码将代码添加到了仓库。
git add chat.java
git commit -m “add chat.java”
由于我们是在分支
talking
下进行聊天代码的添加的。也就是master
没有受到任何影响。从图上也可以看出,当前
master
目录是没有chat.java
文件的。如果我们的聊天功能发现了一个不可修复的bug,我们只好放弃talking
分支,那么我们可以先切换到master
分支,然后通过使用git branch -d
命令来将talking
分支移除。
git checkout master
git branch -D talking
git checkout
的另外一个功能是恢复到修改之前的代码。假如:HelloWorld.java
的原来代码为经过一番
coding
之后,代码变得面目全非了。而由于代码过于混乱,导致之前正确的代码也变得不可用,此时需要恢复到当前未修改的状态,可以使用
git checkout
命令来进行恢复。
git checkout – HelloWorld.java
4.9 Git merge(合并分支)
上一节我们在分支talking
下开发了一个聊天功能,如果开发成功了,我们需要将talking
分支与master
分支进行合并,此时需要使用git merge
命令来进行合并。我们要提交talking
分支到本地仓库,然后切换到master
分支,使用git merge talking
来进行合并,如果没有冲突,那么master
分支将会和talking
分支合并。合并成功,可以看到
master
分支下已经有了chat.java
聊天功能的文件了。
4.10 解决冲突
当我们在使用git merge
命令时,如果没有冲突都会自动合并,否则将会提示哪些文件产生了冲突。冲突主要是由于多个人同时修改了同一个文件的相同地方所造成的,Git
系统不知道应该使用哪部分的代码。解决冲突的方式就是让开发人员自己选择需要保留哪一部分的代码和删除哪一部分的代码。
比如,开发A和开发B共同开发chat.java
聊天文件,两人都修改了函数eat()
内部的代码,此时开发A先添加到了线上的版本控制系统,而此时开发B从线上系统里checkout
出最新代码,这时就会发生冲突,需要开发人员来手动进行合并。
由于在本地测试比较方便,这里就通过在不同分支修改同一文件,然后再进行合并,由于相同文件内的相应方法同时被修改,此时,也会产生冲突,需要解决冲突。开发人员都修改了
eat()
方法。此时,使用git merge
来合并代码会报冲突。图片上提示,需要打开
chat.java
来进行合并代码。>>>>>> HEAD
表示冲突从此处开始,======
表示分割代码冲突的两部分,>>>>>talking
表示冲突结束的地方,这里的talking
表示冲突的分支。到底要留下个部分或者两个部分的代码都留下,这要由开发人员来判定了。这里我们需要移除分支talking
的代码。移除后,代码就变成这样子了。
4.11 Git tag(为版本打标签)
当我们的一个迭代的版本的所有功能都开发完毕,而且都已经经过测试之后,此时,我们通过会为此版本来进行一个标记,也就是为此版本打一个标签。建议大家为每个正式的版本添加一个标签,为便于方便后续版本的检索与维护。通过git tag
命令可以查看当前项目的所有标签信息。新建一个tag
的命令为:
git tag -a 标签名 -m “这里为描述信息” //创建标签
通过
git show 标签名 //查看当前标签信息
通过
git tag -d 标签名 //删除标签
4.12 Git help(帮助文档)
上面所说的一些都是常用的命令,并没有讲得很深,很全。如果对某些命令不是很清楚,或者想了解一些高级的功能,可以通过使用git help
查看帮助文档来进行了解。比如我们想要查看tag
的相关说明,则使用git help tag
来进行查看。这里主要有四项,需要说明一个每一项显示的内容。
NAME
:描述git tag
命令的作用简介SYNOPSIS
:该git tag
命令的参数简介DESCRIPTION
:该命令的功能及相关参数的介绍OPTIONS
:各参数的详细说明
5.项目协作——github
github
是一个共享虚拟主机服务,用于存放通过Git
进行版本控制的软件代码和项目。github
分为两种用户:一种是免费用户;另一种是付费用户。两个用户类型都可以在github
创建公开的仓库。不同的是,付费用户可以在github
上创建私有的、不对外公开的仓库。github
还提供图表功能,使用图表的方式展示开发者怎么在代码仓库上工作及其开发活跃度。学习使用github
来进行版本控制、多人协作将是你必备的技能,也是你参与到开源世界的第一步。
5.1 SSH key配置
要使用github
的第一步就是注册帐号。在注册完帐号之后,为了避免每次进行交互时都需要输入帐号名和密码,我们需要在本地配置生成一个SSH key
,并将此SSH key
添加到github
.添加SSH key
的目的是:使得在使用远程命令时github
能够识别我们的机器,并给予相应的操作权限。
1.生成SSH key
首先在本机中生成一个SSH key
,使用下面的命令生成:
ssh-keygen -t rsa -b 4096 -C “youremail” //youremail代表你在github上注册时所使用的邮箱
此时会询问你文件保存在哪个目录,这里默认保存的是/Users/你的用户名/.ssh目录下。直接回车即可。回车之后,就依次输出如下提示.
这里是输入密码和确认密码。这里可以直接回车,就相当于没有输入密码。输入完毕之后,就会出现下面的结果。
上面的含义是在/Users/mac/目录下生成了
aaa
和aaa.pub
两个文件。这里主要是因为我重新配置了生成文件所存放的目录,所以这里的文件为aaa
和aaa.pub
,如果没有改变的情况下,应该是在.ssh
目录下生成了id_rsa
和id_rsa.pub
两个文件。
操作到这里,只是在本地生成了SSH key
文件。接下来我们需要在Github
上绑定该生成的key
。我们首先需要打开.pub
文件,这里我们的例子是aaa.pub
文件。文件内容如下:最后是你的邮箱。复制这段
key
文字,然后,打开github
,进入个人页面,在Personal settings
的SSH and GPG keys
里选择添加新key
,也就是点击new SSH key
.点击后出现下面页面。为此key取一个名字,然后将复制的本地
key
信息复制到这里。然后点击Add SSH key
来完成对key
的添加。如果设置没错的话,此时本地
SSH key
就与Github
关联上了。可以通过
git -T youremail //这里的youremail替换成你在github上注册时用的邮箱
如果出现下面提示,则说明配置成功。好吧,开始你的
github
开源之旅吧!
5.2项目托管——git remote
我们前面创建了git_project
,然后又配置好了SSH key
,接下来我们需要把本地的git_project
托管到github
上。首先需要在github
上新建一个项目。新建完项目之后,复制项目中的路径。
需要在本地项目里进行相关配置,将
github
上的项目和本地项目进行关联。命令如下:
git remote add origin github项目路径地址
通过上述命令我们就为本地项目添加了一个名为
origin
、地址为git@github.com:allen218/git_project.git
的远程仓库。可以通过git remote -v
命令来查看本地项目的远程仓库地址。一个项目可以对应多个远程仓库,你可以在进行
push
或者pull
时来进行切换。
5.3项目推送到远程仓库——git push
本地项目和github
关联之后,就可以在本地项目和远程仓库之间进行同步数据了。用户可以将本地项目同步到远程仓库,实现在线的版本控制。在进行同步之前,应该确保你的本地状态为已提交,也就是使用了git commit
提交了代码之后的状态。
我们通过git push
来将本地项目推送到远程仓库(这里为github)中。命令格式为:
git push 远程仓库名(这里为上面配置的origin) 分支名或tag名
如,上面的项目git_project
提交到github
使用的命令为:
git push origin master:master
我们的分支名为master:master
,第一个master
代表本地的master
分支;第二个远程的master
分支,也就是将本地的master
添加到远程的master
分支上。如果本地分支名和远程分支名为一致,只需写一个分支名即可。可以简写为:
git push origin master
如果需要将本地的net
分支添加到远程master
分支,则使用net:master
,如果需要删除远程仓库的某个分支,命令如下:
git push 远程仓库名 :远程分支
也就是第四个参数的本地分支名为空,直接写成:net
就表示要删除远程仓库为net
的分支。
5.4更新最新代码——git pull
git pull
与git push
刚好相反,是将远程仓库的代码合并到本地项目。一般我们在使用git push
之前,好的习惯是先使用git pull
来更新远程仓库的最新代码然后来进行与本地代码的合并。然后再提交代码到远程仓库。如果使用git pull
更新本地代码时,遇到了冲突,则需要在本地解决冲突后,才能提交代码。
5.5 Gitignore忽略文件
一般我们在使用git
来进行版本控制时,会有一些文件是不需要进行控制的,如:生成的APK
文件、本地的配置文件等等。git
提供了一个gitignore
的配置文件来让用户将不需要进行版本控制的文件添加到配置中。这样,git
就不会对文件配置列表中的文件进行版本控制。在使用git
管理的Android
项目中,一般有这样子的一个文件。打开里面的配置如下:
这里对apk、ap_、dex结尾的文件进行了过滤。
5.6 Fork + Pull request 多人协作模式
对于软件开发者来说,提升自己技术最好的途径就是阅读、学习别人的优秀源代码。阅读或者参与开源项目是提升自我的一条重要途径。这就要求我们要通过一种协作的方式来参与到开源项目当中。不仅仅是参与开源,如果公司使用git
进行代码托管,也需要这种协作机制。在github
上常用的协作方式就是fork + pull
的方式来进行的。首先对要参与的开源项目进行fork
。fork
操作相当于把原项目的代码copy
了一份到自己的仓库下。派生的代码都有一个指向原项目的链接。派生项目没有自己的缺陷跟踪系统,而需要使用项目创建者中的缺陷追踪系统。
fork
项目后,使用git clone
将代码cp到本地。然后进行项目的修改。然后先将本地修改后的代码提交到github
派生项目中,最后再通过向项目创建者发送Pull request
将自己的代码请求更新到主仓库。,主仓库管理者如果合并了派生项目中的请求,那么派生项目中的代码就会被更新到主仓库中。否则派生项目的代码不会影响主仓库的代码。
这样一来,主仓库的代码不会被随意的更改,所有的改变都必须由主仓库管理者审核;另一方面,派生项目也可以在自己的项目内任意修改代码,而不会影响到主仓库。这样一来,项目就可以接收来自外界的代码,使项目越来越强大、健壮。
总结Fork + Pull
模式有如下几步:
Fork
主仓库得到派生项目。- 通过
Git Clone
或者Git pull
将派生项目的代码下拉到本地。- 本地修改代码实现功能。
- 通过
git add
和git commit
命令提交代码到本地。- 通过
git remote
配置主仓库为本地项目的远程仓库。- 通过
git pull
命令从主仓库对应的分支更新代码,如有冲突则解决冲突。- 将本地项目代码提交到派生项目中去。
- 向主仓库发送
pull request
请求。- 主仓库管理员合并
pull request
。- 到此完成一轮协作。