- CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统。集中式和分布式版本控制系统的区别
集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。所以集中式版本控制系统最大的毛病就是必须联网才能工作,如果在局域网内还好,带宽够大,速度够快。如果在互联网上,则速度难以保证。
而分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
- 安装后使用
因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。所以安装完后使用前注意要进行设置,输入:
$ git config --global user.name "Your Name" $ git config --global user.email "email@example.com"
注意git config
命令的--global
参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
对于git的命令的使用,在终端输入git,得到git以下的命令列表:
对于一些常规的命令的使用还是比较简单的,而且上图的说明很详细。这里不做太多介绍(大家可以继续往后看,后面会有总结)。
- git工作原理(工作区和暂存区)
工作区就是你在电脑里能看到的目录,即本地目录。工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD(分支和
。HEAD
的概念之后再讲)
现在理一下思路,涉及工作区和版本库,可见目录即是我们的工作区,隐藏目录.git即是版本库。版本库分为了暂存区(stage)和分支(master)。个人认为版本库涉及成暂存区和分支这种分了两个层级,容错性较好,比如你对其中的文件做了修改,可能想放弃修改,这是一回事,然后确定保存这个,是否提交又是另一回事。这比直接确定保存修改并提交这样一个层级容错性较好。
了解git工作原理后,我们再来看看一些命令的具体功能。这样我们对常用的命令走一遍加深理解,并熟悉命令的使用及流程。
首先,对于创建一个空目录,用init命令初始化,这使得该目录变成Git可以管理的仓库。然后新建一个文件(注意所有的版本控制系统,其实只能跟踪文本文件的改动,不幸的是,Microsoft的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的),把这个文件放入新建的空目录下,子目录也行(这是一个Git仓库,放到其他地方Git再厉害也找不到这个文件)。然后输入add命令,add命令的作用即是把文件修改添加到暂存区,然后确定提交则调用commit命令,即把暂存区内容提交到分支,一般commit命令必须带提交说明,跟在-m后。
checkout命令的意思就是,把文件在工作区的修改全部撤销,这里有两种情况:文件
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;文件
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。总之,checkout就是让这个文件回到最近一次git commit
或git add
时的状态。
reset的命令既可以回退版本,也可以把暂存区的修改回退到工作区。要进行版本回退,Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,也就是最新的提交3628164...882e1e0
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100。
当我们用HEAD
时,表示最新的版本。从而用命令git reset HEAD file
意思相当于把暂存区的修改撤销掉(unstage),重新放回工作区。而版本回退更常见的做法是先调用git log命令,这个命令会列出提交历史列表,每次提交说明前都有该版本对应的commit id。找到想要回退的id,使用reset命令即可(id可不用全写,写到可区分即可),而对于想回到未来,可先调用git reflog
查看命令历史,以便确定要回到未来的哪个版本。
总结一下,如果进行了错误的修改,但这修改没有add或commit,则直接调用checkout命令即可。如果进行了错误的修改,并且add或commit了,则需要用reset命令进行恢复。
删除文件rm,在Git中,删除也是一个修改操作。确实要删除文件,则删除文件后,调用git rm命令,然后提交即可。命令git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
- 分支管理
在版本回退里已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
从上面可以看出Git创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化。从而之后的修改提交都是针对dev分支了,master分支就不动了,而dev分支则不断往后延长,在dev分支完成工作后,可以把dev分支合并到master分支,即直接把master指向dev当前提交,所以合并操作也很快。然后就可以把dev分支删除,git鼓励创建分支进行工作。相关命令:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
合并冲突:以上提到两个分支合并时,只有一个分支进行了提交,可以使用“快速合并”模式,如果合并的时候两个分支都进行了新的提交,这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,这时必须解决冲突后再提交。可以用git status查看哪个文件冲突到相关文件修改一致后再提交。用git log --graph
命令可以看到分支合并图。
使用fast forward模式(快速合并)合并时,删除分支后会丢失分支信息。如果要强制禁用Fast forward
模式(加--no-ff参数),Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。具体什么意思呢,就是用--no-ff模式合并它的合并图如下,就是会创建一个新的提交,相当于把分支提交到最后合并版本,这样可以看出中间有分支。所以这种模式调用命令如下:git merge --no-ff -m "merge with no-ff" dev后面要加提交参数即-m。
分支策略:首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;干活都在dev
分支上,也就是说,dev
分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev
分支合并到master
上,在master
分支发布1.0版本;你和你的小伙伴们每个人都在dev
分支上干活,每个人都有自己的分支,时不时地往dev
分支上合并就可以了。
Bug分支:这是什么意思呢?就是我们正在工作的时候突然要修改bug(这个最好创建分支解决),这时我们可以用git stash将当前工作区内容保存起来,然后再创建分支,修改完毕后,再调用git stash apply
恢复工作区,并且调用git stash drop
来删除stash内容。或者直接调用git stash pop
,恢复的同时把stash内容也删了。
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除
- 远程操作
因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。虽然github也支持http协议传输,但每次要输入用户名和密码比较麻烦。所以在进行远程操作前最好设置一下。具体操作:
首先在shell中输入
$ ssh-keygen -t rsa -C "youremail@example.com"
一路回车后,在当前目录下的.ssh中有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。然后在GitHub个人设置的ssh keys中点add new key,将id_rsa.pub中的内容全部复制即可,标题可随便写。
从远程库克隆:首先调用如下命令即可将远程库克隆到本地:
$ git clone git@github.com:michaelliao/gitskills.git
实际上,Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh
协议而只能用https
。
所以这里一定要用git不要用http的形式,因为只有用git的ssh协议才能每次提交不用输密码之类的,如果用http之后每次提交每次要输密码,很不方便。
当你从远程仓库克隆时,实际上Git自动把本地的master
分支和远程的master
分支对应起来了,并且,远程仓库的默认名称是origin。要查看远程库的信息,用
git remote,注意这句命令只能在下载下来的目录里运行,不然不会有反应。用
git remote -v
显示更详细的信息,所使用的协议等。
$ git remote -v origin git@github.com:michaelliao/learngit.git (fetch) origin git@github.com:michaelliao/learngit.git (push)
上面显示了可以抓取和推送的origin
的地址。如果没有推送权限,就看不到push的地址。
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,
$ git push origin master
Git就会把该分支推送到远程库对应的远程分支上,当然也可以指定其他分支,如dev。
当从远程库clone时,默认情况下,只能看到本地的master
分支。如果要在dev
分支上开发,就必须创建远程origin
的dev
分支到本地,使用如下命令:
$ git checkout -b dev origin/dev
在dev分支上进行修改后,可以用git push origin branch-name
推送自己的修改;如果推送失败,则因为远程分支比你的本地更新,需要先用git pull
试图合并;如果合并有冲突,则解决冲突,并在本地提交;没有冲突或者解决掉冲突后,再用git push origin branch-name
推送就能成功!如果git pull
提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name。以上是多人协作的模式。
添加远程库:即已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作。具体为
首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库比如leangit。之后可以在本地的仓库下运行命令:
$ git remote add origin git@github.com:michaelliao/learngit.git
这里要注意,把上面的michaelliao
替换成你自己的GitHub账户名,否则,你在本地关联的就是别人的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key公钥不在别人的账户列表中。添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库。
下一步,就可以把本地库的所有内容推送到远程库上,由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令(不用-u参数)。
$ git push -u origin master
- 标签管理
Git的标签是版本库的快照,它其实就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。说白了标签就是某一commit版本的版本号别名,方便查找管理。
创建标签:在Git中打标签非常简单,首先,切换到需要打标签的分支上,然后敲命令git tag <name>
就可以打一个新标签。默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?方法是找到历史提交的commit id,然后打上就可以了git tag <name> <commit id>。可以用命令git tag
查看所有标签。还可以创建带有说明的标签,用-a
指定标签名,-m
指定说明文字。用命令git show <tagname>
可以看到说明文字。
$ git tag -a v0.1 -m "version 0.1 released" 3628164
删除标签:命令git tag -d <tagname>
可以删除一个本地标签。命令git push origin :refs/tags/<tagname>
可以删除一个远程标签。
推送标签:命令git push origin <tagname>
可以推送一个本地标签,命令git push origin --tags
可以推送全部未推送过的本地标签。
参考资料:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000