Git 诞生
linux之父Linus为了更好的管理他自己的开源项目linux系统而自己开发了一个软件,这个软件可以帮助Linux更好的进行开源开发,名字就叫Git。
它是个分布式版本控制系统,Git是最当前流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
安装Git
ubuntu下:
sudo apt-get install gi
可以直接通过源码安装,先从Git官网下载源码,然后解压,之后:
./config
make
sudo make install
创建版本库
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”
创建本地仓库的两种方法:
- 从服务器上克隆仓库:git clone [email protected]:slaming/Markdown.git
- 在工作区初始化仓库: git init
创建完版本库,当前路径里就会发现一个.git的文件夹,这个就是Git的版本库。Git版本库里面有一个称为stage(或者叫index)的暂存区,和还有Git为我们自动创建的第一个分支master,以及指向master的第一个指针HEAD,示意图如下:
将工作区的更改加入本地仓库
我们把文件往Git版本库里添加的时候,是分两步执行的:
(1) 用 git add 命令把变化加入到暂存区
- git add .
- git add 文件名
(2)用 git commit 将缓存区的所有内容提交到当前分支,由于我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
- git commit -m "提交描述"
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的,利用git status查看:
$ git status
On branch master
nothing to commit, working tree clean
关联远程仓库
(1)与github远程仓库关联
$ git remote add origin [email protected]:slaming/learngit.git
添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。
(2)本地仓库向远程仓库提交
$ git push -u origin master
如果远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
本地创建新的分支
(1)常用命令
创建新的分支:
$ git branch dev
切换到dev分支:
$ git checkout dev
为了简单,常用下面命令,创建新的分支dev,并且切换到dev分支:
$ git checkout -b dev
查看版本库有什么分支,git branch命令会列出所有分支,当前分支前面会标一个*号:
$ git branch
* dev
master
合并分支:
$ git merge dev
(2)创建与合并分支
每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点
,每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长:
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是切换到master分支,直接把master指向dev的当前提交,这样就完成了合并:
(3) 分支合并的冲突
假如开辟了新的分支features1
$ git checkout -b feature1
修改readme.txt最后一行,改为:
Creating a new branch is quick AND simple.
提交:
$ git add readme.txt
$ git commit -m "AND simple"
切换到master分支:
$ git checkout master
在master分支上把readme.txt文件的最后一行改为:
Creating a new branch is quick & simple.
提交:
$ git add readme.txt
$ git commit -m "& simple"
合并:
$ git merge feature1
这时Git不能自动合并,会出现冲突。
git merge 的原理
三路合并
三路合并就是先找出一个基准,然后以基准为Base 进行合并,如果2个文件相对基准(base)都发生了改变 那git 就报冲突,然后让你人工决断。否则,git将取相对于基准(base)变化的那个为最终结果。
Base 代表上一个版本,即公共祖先
Mine 代表你本地修改
Theirs 代表其他人修改
这样当git进行合并的时候,git就知道是其他人修改了,本地没有更改,git就会自动把最终结果变成如下,这个结构也是大多merge 工具的常见布局。
如果换成下面的这样,就需要人工解决了:
上面就是git merge 最基本的原理 “三路合并”。
下面的合并就是我们常见的分支graph,结合具体分析
上面①~⑨代表一个个修改集合(commit)每个commit都有一个唯一7位SHA-1唯一表示。
①,②,④,⑦修改集串联起来就是一个链,此时用master指向这个集合就代表master分支,分支本质是一个快照,其实类比C中指针
同样dev分支也是由一个个commit组成
现在在dev分支上由于各种原因要运行git merge master需要把master分支的更新合并到dev分支上,本质上就是合并修改集 ⑦(Mine) 和 ⑧(Theirs) ,此时我们要 利用DAG(有向无环图)相关算法找到我们公共的祖先 ②(Base)然后进行三方合并,最后合并生成 ⑨。
多人协作
你从远程仓库克隆到本地,就可以开始你的开发了。
要查看远程库的信息,用git remote:
$ git remote
origin
或者,用git remote -v显示更详细的信息:
$ git remote -v
origin [email protected]:slaming/learngit.git (fetch)
origin [email protected]:slaming/learngit.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
把分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
$ git push origin dev
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
- dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
- bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。
当你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的地方作了修改,并试图推送:
$ git push origin dev
推送会失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,和merge类似。这时需要手动改写。