集中式和分布式的区别:svn和git
先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
集中式的弊端:必须联网,而且在互联网的情况下,网速会很慢。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
下载版本,创建版本库:
安装成功后:
下载安装包链接:https://git-scm.com/download/win
安装目录是:E:\Program Files\PortableGit
安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!
做配置:
$ git config --global user.name "dongfang"
$ git config --global user.email "[email protected]"
注意git config命令的--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
git上:创建版本库:如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
cd : change directory的简写,改变目录的意思,就是切换到哪个目录下, 如 cd e:\fff 切换 E 盘下面的fff 目录。
新建一个版本库的目录:
mkdir learngit
cd learngit
git init 进行初始化;
E:\git_df\learngit\.git目录不要随意改动目录下的文件,否则会把Git仓库给破坏了。
把文件添加到版本库:
使用Windows的童鞋要特别注意:
千万不要使用Windows自带的记事本编辑任何文本文件。
建议你下载Notepad++代替记事本,不但功能强大,而且免费!记得把Notepad++的默认编码设置为UTF-8 without BOM即可。---设置---UTF-8
书写一个readme.txt,上传到仓库中:
简单解释一下git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。-m的信息最好不要省略。
git commit命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)。
为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:
总结:
现在总结一下今天学的两点内容:
初始化一个Git仓库,使用git init命令。
添加文件到Git仓库,分两步:
- 使用命令git add <file>,注意,可反复多次使用,添加多个文件;
- 使用命令git commit -m <message>,完成。
Git时光穿梭机:回退版本
总结;
要随时掌握工作区的状态,使用git status命令。 如果git status告诉你有文件被修改过,用git diff可以查看修改内容。
版本回退:
git log命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是append GPL,上一次是add distributed,最早的一次是wrote a readme file。
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数:
commit id(版本号)而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,是唯一的,因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
回退版本:
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交1094adb...(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
git reset --hard HEAD^ 回退到倒数第一个版本
git reset --hard HEAD^^ 回退到倒数第二个版本
git reset --hard 1094a ---回退到指定的版本:1094a(是commit id的前几个)
回退之后,还想知道曾经最新的版本,可以用命令:git reflog(记录每一步操作)
总结:
HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本
暂存区:Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念
工作区:就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工作区:
版本库(Repository):工作区一个隐藏目录.git,这个不算工作区,而是Git的版本库。Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
git add file1-n :是把文件1-n都放在暂存区,git commit 是一次性把暂存区的所有修改都提交到master分支中。
暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。
管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
第一次修改 -> git add -> 第二次修改 -> git add -> git commit
现在,你又理解了Git是如何跟踪修改的,每次修改,如果不用git add到暂存区,那就不会加入到commit中。
撤销修改
git checkout -- file可以丢弃工作区的修改:
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区:
小结:
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
删除文件
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
命令git rm用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
Git分支管理
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
创建和合并分支
head指针指向谁,就是这个分支。
head指向master,就是master分支,
指向dev,就是dev分支;
切换到git的目录E:\git_df\learngit\.git,再执行git命令:
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
解决冲突:
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
用git log --graph命令可以看到分支合并图。
git log --graph --pretty=oneline --abbrev-commit 查看分支合并细节
分支管理策略:
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev f52c633] add merge
1 file changed, 1 insertion(+)
现在,我们切换回master:
$ git checkout master
Switched to branch 'master'
准备合并dev分支,请注意--no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...
可以看到,不使用Fast forward模式,merge后就像这样:
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
Git分支十分强大,在团队开发中应该充分应用。
合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
修改bug时:
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
把工作区的内容保存:
git stash :把工作区的内容保存
git stash list:查看工作区的内容
git stash pop:恢复的同时把stash内容也删了
git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。
开发一个新feature,最好新建一个分支;
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>强行删除。
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定!
小结:
查看远程库信息,使用git remote -v;
本地新建的分支如果不推送到远程,对其他人就是不可见的;
从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name; 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。
Git账号、无密码登录、克隆仓库
GitHub的神奇的网站,顾名思义,该网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。
在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
github的用户名和密码:
Sunnyfang3
https://github.com/Sunnyfang3/learngit.git
在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
ssh-keygen -t rsa -C "[email protected]"
你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:
点“Add Key”,你就应该看到已经添加的Key:
生成ssh秘钥:
C:\Users\zhanglei\.ssh
往github上添加本地的仓库,保证本地仓库和github上的库保持一致:
git remote add origin [email protected]:Sunnyfang3/learngit.git
下一步,就可以把本地库的所有内容推送到远程库上:
git push -u origin master ---第一次推时,就添加-u这个参数
把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。
以后再将本地的库内容
$ git push origin master
遇到小问题:当连接暂时无法建立时,要选择yes,进行重试;(估计跟本地网络问题有关。)
总结:
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
如果克隆一个库:
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone命令克隆。例如在本地:
git clone [email protected]:michaelliao/gitskills.git
Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。
标签管理:
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
创建标签:
git tag <name>就可以打一个新标签:默认标签是打在最新提交的commit上的。
git tag查看所有标签:
git tag v0.9 f52c633 对某个commit id对象的版本打tag。方便对历史版本进行打tag‘
git show <tagname>查看标签信息:
git tag不是按照打tag的时间排序的,是按照字母排序的。
git tag -a v0.1 -m "version 0.1 released" 1094adb:还可以创建带说明的标签,用-a指定标签名,-m指定说明文字
注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
git tag -d tagname 删除一个标签:git tag -d v0.1;因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
git push origin <tagname> :如果要推送某个标签到远程
git push origin --tags或者,一次性推送全部尚未推送到远程的本地标签:
如果标签已经推到远程了,要先删除本地的标签,然后再推送到远程即可;
git tag -d v0.9
git push origin :refs/tags/v0.9
要看看是否真的从远程库删除了标签,可以登陆GitHub查看。
小结:
命令git push origin <tagname>可以推送一个本地标签;
命令git push origin --tags可以推送全部未推送过的本地标签;
命令git tag -d <tagname>可以删除一个本地标签;
命令git push origin :refs/tags/<tagname>可以删除一个远程标签
小结:
在GitHub上,可以任意Fork开源仓库;
自己拥有Fork后的仓库的读写权限;
可以推送pull request给官方仓库来贡献代码。
码云:
如果我们希望体验Git飞一般的速度,可以使用国内的Git托管服务——码云(gitee.com)。
和GitHub相比,码云也提供免费的Git仓库。此外,还集成了代码质量检测、项目演示等功能。对于团队协作开发,码云还提供了项目管理、代码托管、文档管理的服务,5人以下小团队免费。
码云的免费版本也提供私有库功能,只是有5人的成员上限。
自定义git:
配置别名:配置方法和shell命令中的差不多;
当然还别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch:
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
git ci -m "bala bala bala..."
配置文件:
配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中:
给Git配置好别名,就可以输入命令时偷个懒。我们鼓励偷懒
管理git服务器,简单一看:
小结
搭建Git服务器非常简单,通常10分钟即可完成;
要方便管理公钥,用Gitosis;
要像SVN那样变态地控制权限,用Gitolite。