Git简单介绍
参考网址:
这个教程推荐使用:git教程
git和svn的差异
git和svn的最大差异在于git是分布式的管理方式而svn是集中式的管理方式。如果不习惯用代码管理工具,可能比较难理解分布式管理和集中式管理的概念。下面介绍两种工具的工作流程(团队开发),通过阅读下面的工作流程,你将会很好的理解以上两种概念。
集中式管理
集中式管理的工作流程如下图:
集中式管理的核心是服务器,所有的开发者在开始的新一天的工作之前必须要从服务器获取代码,然后开发,最后解决冲突,提交。所有的版本信息都放在服务器上面。如果脱离了服务器,开发者基本上就不可以工作了。一次开发者需要老是对代码进行更新,老是会有冲突需要解决。
svn有下面的优缺点:
优点:
- 管理方便,逻辑明确,符合我们的思维逻辑
- 易于管理,集中式服务器更能保证安全性
- 代码一致性高
- 适合开发人数不多的项目开发
缺点
- 服务器压力太大
- 如果我们连接不到服务器上,基本上就是不可以工作了。因为我们不可以从服务器上进行操作了,不可以下载和提交。
- 不适合人数多的人开发
分布式管理
分布式管理的工作流程如下图:
分布式和集中式的最大区别就在于开发者可以在本地提交。每一个开发者机器上都有一个服务器的数据库。
根据上面的分布式管理的图片,对分布式管理进行简单介绍:
一般开发者的角度:
- 从服务器上克隆数据库(包括代码和版本信息)到单机上
- 在自己的机器上创建分支,修改代码
- 在单机上自己创建的分支上提交的代码
- 在单机上合并分支
- 建立一个分支,把服务器上最新版的代码fetch下来,然后跟自己的主分支合并
- 生成补丁,把补丁发送给主开发者
- 看主开发者的反馈,如果主开发者发现两个开发者之间有冲突,要求他们进行协调,然后一个人提交。
- 一般开发者之间解决冲突的方法,开发者之间可以使用pull命令解决冲突,解决完冲突之后再向主开发者提交补丁
主开发者的角度
- 查看邮件或者通过其他方式查看一般开发者的提交状态
- 打上补丁,解决冲突
- 向公共服务器提交结果,然后通知所有开发者人员
优点
- 适合分布式开发,强调个体
- 公共服务器压力和数据量都不会太大
- 速度快,灵活
- 任意两个开发者之间都可以解决冲突
Git存储空间
git中有两个空间,工作区,版本库
工作区
工作区(working directory)就是你在电脑里能看到的目录,比如我创建了一个learnGit的目录,然后在这个目录里面git init
创建了git库,那么这个文件夹就是工作区。
版本库
版本库(repository)就是工作区中的一个隐藏目录.git
,这个不算工作区,而是git的版本库。git里面有许多东西,其中最重要的就是称为stage的暂存区,还有git为我们自动创建的第一个分支master
,以及指向master
的一个指针head
添加过程
我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
)
Git分支工作状态
在每次提交的时候,git都把他们串成一条时间线,这条时间线就是一个分支。下面是一个例子,这里例子中是有一条主分支master
分支,HEAD
严格来说不是指向提交的,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支.
一开始的时候,master
分支是一条线,git用master
指向最新的提交,在用HEAD
指向master
,就能确定当前分支以及分支的提交点:
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长.
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
Git远程仓库的创建
github网站为我们提供了一个免费的git仓库托管服务,我们可以在这个网站上建立远程代码仓库。
由于你的本地的Git仓库和github仓库之间的传输是通过ssh加密的,所以需要一点的设置:
创建SSH Key。在用户主目录下,看看有没有.ssh,注意.ssh是一个隐藏文件,我们需要命令行
ls -ah
来查看,如果有,看进入到这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,就直接跳过下一步。如果没有,那么在终端中创建一个SSH Key$ ssh-keygen -t rsa -C "youremail@example.com"
你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这 个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和 id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露 出去,id_rsa.pub是公钥,可以放心地告诉任何人。
登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容
)
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
Git命令行
git仓库创建
$ mkdir learngit//创建一个目录
$ cd learngit//进入目录
$ pwd//查看所处文件位置
/Users/michael/learngit
$git init//将目录改为git仓库
$ls -ah//显示隐藏文件,里面有一个.git文件,就代表了创建git成功
向git仓库里面添加文件,并提交
$ git add readme.txt
$ git commit -m "wrote a readme file"//-m后面添加commit的时候的解释。便于合作开发的理解。
[master (root-commit) cb926e7] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
git中查看工作区的状态
$ git status
$ git diff readme.txt //查看这个readme.txt文件的改变
git版本回退
Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交3628164…882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
下面是版本回退的编码
$ git reset --hard HEAD^//回退到上一个版本
$ git reset --hard 3628164//回退到id号为3628164的版本,这个版本号(3628164)不需要写全,系统会搜索
$ git log//可以查看当前版
$ git reflog//查看所有版本
穿梭前,用git log
可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog
查看命令历史,以便确定要回到未来的哪个版本。
git撤销修改
$ git checkout -- readme.txt
当修改还没有add到暂存区里面的时候,可以使用上面的命令行来撤销对readme.txt文件的修改
$ git reset HEAD readme.txt
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。这句话要在我们已经将修改传到了暂存区里面的时候,我们就使用命令行来将暂存区中的修改退回,然后在使用一开始的命令行来撤销readme.txt的修改
git添加远程代码仓库
首先在github里面创建一个远程代码仓库,然后将本地库与远程库进行关联在本地创建的learngit仓库中运行下面命令:
$ git remote add origin git@github.com:michaelliao/learngit.git
请千万注意,把上面的
michaelliao
替换成你自己的GitHub
账户名,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key
公钥不在我的账户列表中。添加后,远程库的名字就是
origin
,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库。下一步,将本地库中的内容添加到到远程库中:3.
$ git push -u origin master
把本地库的内容推送到远程,用
git push
命令,实际上是把当前分支master推送到远程。由于远程库是空的,我们第一次推送
master
分支时,加上了-u
参数,Git不但 会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。之后,我们要做本地提交,执行下面命令即可:
$ git push origin master
git克隆远程代码库
$ git clone git@github.com:michaelliao/gitskills.git
在本地仓库中运行命令行来克隆远程代码库
创建与合并分支
可能我总结的不是很好,下面是原版大神教程:git教程——创建与合并分支
创建一个new分支,然后查看分支
$ git checkout -b dev
$ git branch
然后更改工作区中的文件
$git add readme.txt
$git commit -m"branch test"
然后切换到master
分支中去
$ git checkout master
我们发现刚刚添加的更改不见了,因为我们切换到了另一个分支,这个分支的指针还是指向原来状态的文件的。因此没有看到改变。
最关键的工作来了,就是合并两个分支:现在,我们把dev分支的工作成果合并到master分支上
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
删除分支命令行:
$ git branch -d dev
git推送分支
重点:我总结的不是很好,下面是原版大神教程:git教程——推送分支
推送分支就是把该分支上的所有本地提交到远程库。推送时,要指定本地分支,这样,git就会把该分支推送到远程库对应的远程分支上:
$git push origin master
如果要推送其他分支,只要将master
改为dev
,就ok了
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
git抓取分支
重点:我总结的不是很好,下面是原版大神教程:git教程——抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。
现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆:
$ git clone git@github.com:michaelliao/learngit.git
Cloning into 'learngit'...
remote: Counting objects: 46, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 46 (delta 16), reused 45 (delta 15)
Receiving objects: 100% (46/46), 15.69 KiB | 6 KiB/s, done.
Resolving deltas: 100% (16/16), done.
当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:
$ git branch
* master
现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:
$ git checkout -b dev origin/dev
现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:
$ git commit -m "add /usr/bin/env"
[dev 291bea8] add /usr/bin/env
1 file changed, 1 insertion(+)
$ git push origin dev
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 349 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
fc38031..291bea8 dev -> dev
你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:
$ git add hello.py
$ git commit -m "add coding: utf-8"
[dev bd6ae48] add coding: utf-8
1 file changed, 1 insertion(+)
$ git push origin dev
To git@github.com:michaelliao/learngit.git
! [rejected] dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
fc38031..291bea8 dev -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream dev origin/<branch>
git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
$ git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.
再pull:
$ git pull
Auto-merging hello.py
CONFLICT (content): Merge conflict in hello.py
Automatic merge failed; fix conflicts and then commit the result.
这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:
$ git commit -m "merge & fix hello.py"
[dev adca45d] merge & fix hello.py
$ git push origin dev
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 747 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
291bea8..adca45d 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。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。