Git是一款开源的分布式版本控制系统,它的出现和Linux紧密相关。Linux内核项目组为了能更好地管理和维护Linux内核开发,于2002年开始启用商业的分布式版本控制系统BitKeeper。虽然软件开发商受权了Linux社区能无偿使用,可是好景不长,到了2005年,BitKeeper的开发商因为某些缘由终止了与Linux社区的合做关系。因而Linux的做者Linus Torvalds就决定开发一款能替代BitKeeper的分布式版本控制系统(即Git),在花费十天的时间后发布了Git的第一个版本。html
版本控制系统(Version Control System,VCS)能管理文件内容的变动记录,便可追踪文件的修订历史,确保不一样的人在编辑同一文件时能保持同步。该系统不只能应用于保存源码的文本文件,还能对图像、Word文档等各类类型的文件进行版本控制。有了版本控制系统以后,就能很方便的回退文件到某个状态、比较文件变动先后的区别、查询到修改文件的人等。目前市面上的版本控制系统大体可分为两种:集中式和分布式,下面会对它们作单独的讲解。node
1)集中式git
当须要多人协同工做时,就得让集中式版本控制系统(Centralized Version Control Systems,CVCS)登场了。github
这类系统包括CVS、Subversion等都是在一台中央服务器中创建一个仓库,专门用来管理文件的修订版本,客户端可经过网络链接到这台服务器,取出最新文件或提交变动,如图3所示。正则表达式
图3 集中式版本控制系统服务器
虽然使用CVCS带来了诸多便利,可是其缺点也很明显,例如中央服务器一旦宕机,那么客户端将没法提交变动和协同工做;或者中央服务器磁盘故障,没有备份仓库,那么将丢失全部的变动记录。网络
2)分布式app
为了解决CVCS所带来的问题,又有人提出了分布式版本控制系统(Distributed Version Control System,DVCS)。分布式
这类系统包括Git、BitKeeper等都会将服务器中的仓库完整的克隆到本地,这么一来,即便网络断开,也能继续工做,而且受服务器故障的影响也能下降到最小。在图4中,服务器和两台电脑三者之间都能相互推送各自的修订记录,而且每台电脑上都保存了一个本地仓库。工具
图4 分布式版本控制系统
由此可知,DVCS剥夺了服务器的生杀大权,只让它负责中转你们的修订记录,即便缺了服务器,也不影响协同工做。
Git不会像CVS、Subversion等版本控制系统那样存储每一个文件所变动的内容以及修订文件版本之间的关系。Git更像是一个文件系统,它存储的是文件快照(Snapshot)。
Git会先将那些变动的文件拷贝一份,而后把备份文件转换成Blob对象,并对其进行压缩,再把文件各自的内容经过SHA-1哈希运算出对应Blob的名称(即版本号),以下所示,最后由这些哈希值做为索引组成一个快照(即版本信息),而经过快照就能反推出该版本中全部发生变动的文件内容。
fbcceef922ce47253804cf00c72c2e955b8bc1b3
每次提交都会生成一个新的快照,而对于未更改的文件,则直接连接到上一个版本。
Git的工做区域包含三部分:工做目录、暂存区和仓库,它们的说明以下所列:
(1)工做目录(Working Directory)就是去除项目版本信息后的目录,即磁盘上实际操做的目录。
(2)暂存区(Stage)也叫索引区(Index),是一个记录了变动信息的文件,即保存着变动文件的当前快照,为提交到仓库中作准备。
(3)仓库(Repository)也叫版本库,是一个名为.git的隐藏目录,保存着项目的元数据、快照等信息,Git的仓库可分为远程和本地两种。
1)工做流程
在图5中,经过工做区域的三部分描绘出了Git基本的工做流程,简单的将其分为四步,并给出了相应的Git命令。
图5 Git的工做流程
首先在工做目录中修改源文件,而后用add命令将变动记录保存到暂存区,再用commit命令将暂存区中的文件提交给本地仓库,最后用push命令将本地仓库的信息推送给远程仓库。图中的云朵表示网络,由于通常远程仓库都会被托管在某台服务器中,须要经过网络将本地信息传送过去。
2)三种状态
Git中的文件有多种状态,此处列出其中的三种:已修改(modified)、已暂存(staged)和已提交(committed)。
(1)已修改是还未提交的变动文件。
(2)已暂存是记录在当前快照中的变动文件。
(3)已提交是保存在本地仓库中的变动文件。
本节将列举出Git经常使用的命令,包括建立仓库、提交更新、查看信息等。
1)建立仓库
若是要在当前目录中建立仓库,那么能够用下面的初始化命令。
git init
在执行该命令后,就会生成名为.git的隐藏目录。
2)克隆仓库
GitHub是一个提供Git仓库托管服务的网站,若是要在本机获取网站中已经存在的仓库,那么可使用克隆命令,以下所示。注意,这两条命令的效果是相同的,由于Git支持HTTPS和SSH两种数据传输协议。
git clone https://github.com/pwstrick/demo.git git clone git@github.com:pwstrick/demo.git
在执行该命令后,就会建立一个名为demo的目录。默认状况下,远程仓库中的每一个文件的每一个版本都会被拉取下来。
3)提交更新
工做目录中的文件可分为两大类:已跟踪(tracked)和未跟踪(untracked)。已跟踪的文件是指已经被归入版本控制的文件,它们的状态能够是已修改、已暂存或已提交;而未跟踪的文件正好与之相反,而且不会被记录在以前的快照中。当初始化工做目录时,其中的文件都是未跟踪的;而当克隆仓库时,其中的文件都是已跟踪的。
使用add命令,开始跟踪文件,其后面跟的参数既能够是文件路径,也能够是目录路径,下面命令中的点号表示跟踪当前目录中的全部文件。
git add .
在执行add命令后,工做目录中新增或变动的文件就会被记录在暂存区,而且肯定了下次提交时的快照内容。
当暂存区已准备完毕能够提交时,就能执行commit命令了,以下所示,其中“-m”参数能为这次提交添加备注说明,注意,得用引号包裹。
git commit -m "add files"
commit命令能为本地仓库建立一个持久快照,其内容来自于暂存区的临时快照。commit命令还有一个“-a”参数,可让已经跟踪过的文件直接到暂存区,而后一并提交给本地仓库,从而就能跳过add命令,若是要与“-m”一块儿使用,能够像下面这样。
git commit -am "add files"
固然,对于未跟踪的文件,没法省略add命令,仍然得执行。
4)忽略文件
在工做目录中建立一个名为.gitignore的文件,列出要忽略的匹配模式(以下所示),就能让Git再也不管理相关的文件或目录,例如忽略日志、编译生成的临时文件、Node.js的安装目录等。
# system *.docx node_modules/
第一行至关于注释,第二行是告诉Git忽略后缀为“.docx”的文件,第三行是忽略node_modules目录。.gitignore文件支持Glob模式匹配(即简化过的正则表达式),另外还有一些格式规范:
(1)全部空行或以“#”开头的行都会被Git忽略。
(2)匹配模式能以“/”开头防止递归。
(3)匹配模式能以“/”结尾指定目录。
(4)只要在模式前加上“!”取反,就能忽略该模式之外的文件或目录。
5)删除文件
Git提供了rm命令,可删除工做目录中的指定文件,而且能消除它在暂存区中的记录,以下所示。
git rm demo.txt
rm命令的后面既能够跟文件或目录的名称,也能跟Glob模式的正则表达式,例如像下面这样删除后缀为“.log”的文件。
git rm *.log
结合commit命令就能将删除的操做告知本地仓库。
6)撤销操做
若是要撤销工做目录中的文件变动,可使用checkout命令,以下所示,撤销了demo.txt文件中的变动。注意,命令中的“--”不能省略,不然就会执行切换分支的操做。
git checkout -- demo.txt
若是要撤销暂存区中的文件变动,可使用reset命令,以下所示,其中HEAD是一个指针,指向当前分支,经过它能获得该分支上最后一次提交的快照。
git reset HEAD demo.txt
注意,上述两种撤销都是在commit命令以前执行的。有些状况下的撤销须要依次执行上面两条命令才能完成,例如demo.txt修改了屡次,而且执行了屡次“git add .”命令,而后要撤销全部的更改。
7)回退版本
若是要实现版本回退,那么首先得知道版本号,而经过log命令就能获取到当前分支的历史版本,以下所示,包括版本号、做者、备注等信息。
$ git log commit 00ef86de2fe382c5e1a9185a182a35bbbd34c0fd Author: strick <pwstrick@163.com> Date: Tue Jul 2 14:26:31 2019 +0800 test commit 9a701365eeb47cf876c726cf5e321af5ce9cabbf Author: strick <pwstrick@163.com> Date: Tue Jul 2 14:13:19 2019 +0800 add demo.txt
如今就能经过reset命令,指定版本号,实现回退,以下所示。因为HEAD指针的存在,Git的版本回退其实就是移动指向,所以其速度是很是快的。
git reset --hard 9a701365eeb47cf876c726cf5e321af5ce9cabbf
8)查看信息
在Git中用于查看信息的命令包括log、status、diff和reflog等,其中log命令已在前文介绍过,用来查看提交的版本历史,按提交时间倒序排列。接下来会先修改demo.txt文件,而后再执行相关的查看命令,其中命令可选择的参数因为篇幅缘由都没有给出。
status命令用来查看工做目录的状态,显示变动信息以及提示可用命令,以下所示。
$ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: demo.txt no changes added to commit (use "git add" and/or "git commit -a")
diff命令用来查看工做目录和暂存区之间的差别,以下所示,其中a和b表示的是同一个文件的不一样版本。
$ git diff diff --git a/demo.txt b/demo.txt index d28d40b..30d74d2 100644 --- a/demo.txt +++ b/demo.txt @@ -1 +1 @@ -add \ No newline at end of file +test \ No newline at end of file
diff命令还能比较暂存区和本地仓库、两次commit提交、文件修改先后等之间的差别。
reflog命令用来查看当前分支的全部操做记录,包括commit、reset、pull等,以下所示。
e1ce188 HEAD@{0}: pull origin master: Fast-forward 9a70136 HEAD@{1}: reset: moving to 9a701365eeb47cf876c726cf5e321af5ce9cabbf ...... fb8e5df HEAD@{13}: commit: demo f1f30d6 HEAD@{14}: clone: from https://github.com/pwstrick/demo.git
9)远程仓库
与远程仓库相关的命令有remote、fetch、pull和push,其中remote可与其它命令(例如add、show、rename等)配合实现扩展功能。
remote命令可列出全部远程仓库的简称,在执行克隆命令时,Git会默认为其添加一个名为origin的远程仓库。若是为remote命令添加“-v”参数,那么会显示远程仓库的简称和其读写的URL。
$ git remote -v origin https://github.com/pwstrick/demo.git (fetch) origin https://github.com/pwstrick/demo.git (push)
fetch命令可从远程仓库中拉取本地没有的数据,例如缺失的分支。
pull命令可抓取远程分支的最新变动,并将其自动合并到当前分支中,在pull后面会紧跟远程仓库的简称和分支名称,以下所示。
git pull origin master
push命令可将本地快照、变动的文件等信息推送到远程仓库中,其格式与pull命令相似,以下所示。
git push origin master
Git之因此能高性能的处理分支,主要得益于其不同凡响的存储设计:弃文件变化,取文件快照。每次commit提交,Git都会建立一个提交对象(Commit Object),该对象包含本次快照的指针、父对象的指针(即上一个提交对象)、当前工做目录的结构和相关附属信息(例如做者、备注等),注意,首次提交产生的提交对象没有父对象。
Git的分支本质上就是指向提交对象的可变指针,其默认分支叫master,如图6所示,用圆表明提交对象,圆内的字符串表示版本号的前5位。
图6 分支和其提交历史
1)建立
建立分支其实就是新建一个新的可移动指针,若是要建立一条develop分支,那么执行的命令能够像下面这样。
git branch develop
如今就会变成两条分支,如图7所示。
图7 两条分支
执行“git branch”命令可以查看已有的分支,在当前分支的名称前会加“*”标记,以下所示。
$ git branch
develop
* master
2)切换
在Git中,有一个特殊的HEAD指针,以前曾提到过它能指向当前分支,至关于当前分支的别名,如图8所示。
图8 HEAD指向当前分支
切换分支其实就是改变HEAD的指向,例如切换到develop分支,其命令以下所示,内部操做如图9所示。
git checkout develop
图9 切换分支
有一条快捷命令,能够建立一个分支并自动切换到那个分支,以下所示,给checkout命令添加了“-b”参数。
git checkout -b develop
3)合并
有了分支后,就能在各自的分支工做,而且互不干扰,例如在develop分支提交了一个版本,如图10所示,develop分支向前移动了,而master分支仍在原地。
图10 HEAD自动向前移动
如今用“git checkout master”命令将分支切换回master,HEAD会指向master分支,而且工做目录将恢复成master分支中的快照。若是要将develop分支中的变动记录合并(Merge)到master分支中,那么可使用merge命令,以下所示。
$ git merge develop Updating 9a9636b..64ea4b2 Fast-forward demo.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
注意上面的Fast-forward,说明此次合并使用了快进模式,即直接将指针向前移动。举例来讲,当前开发处在develop分支,在提交一系列变动后,切换到master分支,而master分支没有提交新的变动,此时将develop分支合并到master分支就会采用快进模式。
4)冲突
若是两个分支对同一文件的同一行都作了不一样的修改,那么在合并时就会发生冲突(Conflict),以下所示,都修改了demo.txt文件的第一行。
$ git merge develop Auto-merging demo.txt CONFLICT (content): Merge conflict in demo.txt Automatic merge failed; fix conflicts and then commit the result.
此时,Git就会中止工做,等待冲突的解决。使用status命令能够查看全部冲突的文件,以下所示。
$ git status On branch master Your branch is up-to-date with 'origin/master'. You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: demo.txt no changes added to commit (use "git add" and/or "git commit -a")
查看demo.txt文件,能够看到下面的特殊区段。
<<<<<<< HEAD test ======= minus >>>>>>> develop
Git用<<<<<<<、=======和>>>>>>>标记出不一样分支的变动,其中上半部分是HEAD所指的master分支,下半部分是develop分支。要解决冲突,就得手动去掉Git生成的多余字符,以及保留其中一个分支的修改,在完成这一系列的操做后,demo.txt文件中的内容就会变成下面这样。
minus
如今只要将冲突文件的变动保存到暂存区(以下所示),就能将它们标记成已解决,继续下面的工做了。
git add .
git commit -am "conflict"
当冲突的文件特别多时,一个个的查找不免会费时费力,改用Git客户端工具会便捷不少,例如TortoiseGit,它不只能列出冲突的文件,还能提供图11中的可视化编辑界面。
图11 TortoiseGit冲突解决
5)远程
以前经过push命令将本地分支的信息推送到了远程仓库中(以下所示),与此同时,若是没有远程的同名分支,那么Git会自动为其建立一个,而且还会为它们两个创建跟踪关系。
git push origin master
利用branch命令可在本地查看远程仓库(以下所示),分支会以“远程仓库/分支名称”的形式显示,例如origin/master。
$ git branch -r
origin/develop
origin/master
若是当前分支与远程分支存在跟踪关系,那么在push或pull时可省略分支名称,以下所示。
git pull origin
git push origin
Git容许手动创建跟踪关系,只要为branch命令添加“-u”或“--set-upstream-to”参数便可(以下所示),从而就能让一个本地分支跟踪多个远程分支。
$ git branch -u origin/strick Branch develop set up to track remote branch strick from origin.
若是当前分支只有一个可跟踪的远程分支,那么在push或pull时连仓库名称都能省略,以下所示。
git pull
git push
6)删除
若是要删除分支,那么有两条命令可供选择,下面的第一条可删除本地分支,第二条可删除远程分支,本质上它们作的只是移除相应的指针。
git branch -d develop git push origin --delete develop
7)变基
变基(Rebase)能将一条分支上的变动转移到另外一条分支上,以图12中的master和develop两条分支为例。
图12 变基前的两条分支
假设当前分支是develop,变基的目标分支是master,执行rebase命令后的分支状况如图13所示。
图13 变基后的两条分支
接下来会经过一个例子来说解变基的用法,首先经过log命令查看master分支的提交历史,以下所示,其中“number”是修改demo.txt文件时所提交的备注。
$ git log commit 7c27be31904221f3bb4556ca39dd7c8dea071178 Author: strick <pwstrick@163.com> Date: Wed Jul 3 17:48:47 2019 +0800 number
而后切换到develop分支,并执行rebase命令,以下所示。注意,变基目标是master而不是develop。
$ git checkout develop $ git rebase master First, rewinding head to replay your work on top of it... Applying: digit Using index info to reconstruct a base tree... M demo.txt Falling back to patching base and 3-way merge... Auto-merging demo.txt CONFLICT (content): Merge conflict in demo.txt Failed to merge in the changes. Patch failed at 0001 digit The copy of the patch that failed is found in: D:/demo/.git/rebase-apply/patch When you have resolved this problem, run "git rebase --continue". If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort".
因为两条分支同时修改了demo.txt文件中的内容,所以产生了冲突,必须先将其解决,才能继续后面的操做。注意,develop分支的修改记录先于master分支提交。
当解决了全部冲突以后,先执行add命令,注意,此时不须要commit命令。而后再为rebase命令添加“--continue”(以下所示),就能完成变基,其中“digit”也是修改demo.txt文件时所提交的备注。
$ git add . $ git rebase --continue Applying: digit
接着切换回master分支,并将develop分支中的修改合并进来,注意,此时开启了快进模式。
$ git checkout master $ git merge develop Updating 7c27be3..24c4b5a Fast-forward demo.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
最后执行log命令,就能看到develop分支所提交的版本添加到了master分支的后面,注意,它没有根据提交时间按顺序插入。
$ git log commit 24c4b5adb332f8c4f2e5ec39a7c77e0fc224b065 Author: strick <pwstrick@163.com> Date: Wed Jul 3 17:48:00 2019 +0800 digit commit 7c27be31904221f3bb4556ca39dd7c8dea071178 Author: strick <pwstrick@163.com> Date: Wed Jul 3 17:48:47 2019 +0800 number
变基的一大特色就是能将分叉的提交历史梳理成一条直线,另外一个特色是它会改变提交历史,这与合并彻底不一样。变基还有一条基本原则,即只对还没有推送和分享给他人的本地修改容许执行变基操做。